Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions src/lib/filter-entries.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { test, expect } from '@playwright/test'
import { filter_coverage } from './filter-entries.js'
import { Coverage } from './parse-coverage.js'

test('filters out JS files', () => {
let entries = [
Expand All @@ -8,7 +9,7 @@ test('filters out JS files', () => {
text: 'console.log("Hello world")',
ranges: [{ start: 0, end: 25 }],
},
]
] satisfies Coverage[]
expect(filter_coverage(entries)).toEqual([])
})

Expand All @@ -19,7 +20,7 @@ test('keeps files with CSS extension', () => {
text: 'a{color:red}',
ranges: [{ start: 0, end: 13 }],
},
]
] satisfies Coverage[]
expect(filter_coverage(entries)).toEqual(entries)
})

Expand All @@ -37,7 +38,7 @@ test('keeps extension-less URL with HTML text', () => {
text: 'a{color:red;}',
ranges: [{ start: 0, end: 13 }], // ranges are remapped
},
]
] satisfies Coverage[]
expect(filter_coverage(entries)).toEqual(expected)
})

Expand All @@ -48,6 +49,17 @@ test('keeps extension-less URL with CSS text (running coverage in vite dev mode)
text: 'a{color:red;}',
ranges: [{ start: 0, end: 13 }],
},
]
] satisfies Coverage[]
expect(filter_coverage(entries)).toEqual(entries)
})

test('filters out extension-less JS', () => {
let entries = [
{
url: 'http://example.com',
text: 'var a = 10; console.log(a);',
ranges: [{ start: 0, end: 29 }],
},
] satisfies Coverage[]
expect(filter_coverage(entries)).toEqual([])
})
34 changes: 27 additions & 7 deletions src/lib/filter-entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@ function is_html(text: string): boolean {
return /<\/?(html|body|head|div|span|script|style)/i.test(text)
}

// Matches: element selectors, class/id selectors, attribute selectors, @rules
const SELECTOR_REGEX = /(@[a-z-]+|\[[^\]]+\]|[a-zA-Z_#.-][a-zA-Z0-9_-]*)\s*\{/
// Check for CSS properties (property: value pattern)
const DECLARATION_REGEX = /^\s*[a-zA-Z-]+\s*:\s*.+;?\s*$/m

function is_css_like(text: string): boolean {
return SELECTOR_REGEX.test(text) || DECLARATION_REGEX.test(text)
}

function is_js_like(text: string): boolean {
try {
// Only parses the input, does not execute it.
// NEVER EXECUTE THIS UNTRUSTED CODE!!!
new Function(text)
return true
} catch {
return false
}
}

export function filter_coverage(coverage: Coverage[]): Coverage[] {
let result = []

Expand All @@ -29,13 +49,13 @@ export function filter_coverage(coverage: Coverage[]): Coverage[] {
continue
}

// At this point it can only be CSS
// TODO: that's not true, check if it's css-like of js-like
result.push({
url: entry.url,
text: entry.text,
ranges: entry.ranges,
})
if (is_css_like(entry.text) && !is_js_like(entry.text)) {
result.push({
url: entry.url,
text: entry.text,
ranges: entry.ranges,
})
}
}

return result
Expand Down
18 changes: 12 additions & 6 deletions src/lib/html-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,16 @@ test('finds style tags with attributes', () => {
expect(parse('<style data-attr data-testid="yup">.css{}</style>')).toEqual([{ textContent: '.css{}' }])
})

test('ignores style tags without end tag', () => {
expect(parse('<style>.css{}')).toEqual([])
})

test('ignores style tags without closing opening tag', () => {
expect(parse('<style .css{}')).toEqual([])
test.describe('invalid tags', () => {
test('ignores style tags without end tag', () => {
expect(parse('<style>.css{}')).toEqual([])
})

test('ignores style tags without closing opening tag', () => {
expect(parse('<style .css{}')).toEqual([])
})

test('custom element: <style-thing>', () => {
expect(parse('<style-thing>.css{}</style-thing>')).toEqual([])
})
})