Skip to content

Commit 087a98e

Browse files
committed
Improve import specifier sanitizer (resolves #1257)
1 parent 5b54dae commit 087a98e

3 files changed

Lines changed: 51 additions & 19 deletions

File tree

packages/knip/src/util/modules.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,21 @@ export const getPackageFromDefinitelyTyped = (typedDependency: string) => {
4949
};
5050

5151
// Strip `?search` and other proprietary directives from the specifier (e.g. https://webpack.js.org/concepts/loaders/)
52-
const matchDirectives = /^([?!|-]+)?([^!?:]+).*/;
5352
export const sanitizeSpecifier = (specifier: string) => {
54-
if (isBuiltin(specifier)) return specifier;
55-
if (isAbsolute(specifier)) return specifier;
56-
if (specifier.startsWith(PROTOCOL_VIRTUAL)) return specifier;
57-
return specifier.replace(matchDirectives, '$2');
53+
if (isBuiltin(specifier) || isAbsolute(specifier) || specifier.startsWith(PROTOCOL_VIRTUAL)) return specifier;
54+
let s = specifier;
55+
let end = s.length;
56+
let i = 0;
57+
while (i < s.length && (s[i] === '!' || s[i] === '-')) i++;
58+
s = s.substring(i);
59+
for (let j = 0; j < s.length; j++) {
60+
const char = s[j];
61+
if (char === '!' || char === '?' || (char === '#' && j > 0)) {
62+
end = j;
63+
break;
64+
}
65+
}
66+
s = s.substring(0, end);
67+
if (s.includes(':') && !s.includes('/')) s = s.split(':')[0];
68+
return s;
5869
};

packages/knip/test/util/module.test.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { test } from 'bun:test';
2+
import assert from 'node:assert/strict';
3+
import { getDefinitelyTypedFor, getPackageFromDefinitelyTyped, sanitizeSpecifier } from '../../src/util/modules.js';
4+
5+
test('Should return definitely typed package for package name', () => {
6+
assert.equal(getDefinitelyTypedFor('node'), '@types/node');
7+
assert.equal(getDefinitelyTypedFor('@npmcli/map-workspaces'), '@types/npmcli__map-workspaces');
8+
assert.equal(getDefinitelyTypedFor('@types/node'), '@types/node');
9+
});
10+
11+
test('Should return package name from definitely typed package name', () => {
12+
assert.equal(getPackageFromDefinitelyTyped('node'), 'node');
13+
assert.equal(getPackageFromDefinitelyTyped('npmcli__map-workspaces'), '@npmcli/map-workspaces');
14+
});
15+
16+
test('Should sanitize import specifier', () => {
17+
assert.equal(sanitizeSpecifier('specifier'), 'specifier');
18+
assert.equal(sanitizeSpecifier('/specifier'), '/specifier');
19+
assert.equal(sanitizeSpecifier('./specifier'), './specifier');
20+
assert.equal(sanitizeSpecifier('../specifier.ext'), '../specifier.ext');
21+
assert.equal(sanitizeSpecifier('specifier?query=1'), 'specifier');
22+
assert.equal(sanitizeSpecifier('specifier#hash'), 'specifier');
23+
assert.equal(sanitizeSpecifier('style-loader!css-loader?modules!./styles.css'), 'style-loader');
24+
assert.equal(sanitizeSpecifier('!!style-loader!css-loader?modules!./styles.css'), 'style-loader');
25+
assert.equal(sanitizeSpecifier('-!style-loader!css-loader?modules!./styles.css'), 'style-loader');
26+
assert.equal(sanitizeSpecifier('css-loader?modules!./styles.css'), 'css-loader');
27+
assert.equal(sanitizeSpecifier('./:id/specifier'), './:id/specifier');
28+
assert.equal(sanitizeSpecifier('#specifier'), '#specifier');
29+
assert.equal(sanitizeSpecifier('#id/specifier'), '#id/specifier');
30+
assert.equal(sanitizeSpecifier('~/id/specifier'), '~/id/specifier');
31+
assert.equal(sanitizeSpecifier('astro:content'), 'astro');
32+
assert.equal(sanitizeSpecifier('virtual:specifier'), 'virtual:specifier');
33+
assert.equal(sanitizeSpecifier('fs'), 'fs');
34+
assert.equal(sanitizeSpecifier('node:fs'), 'node:fs');
35+
});

0 commit comments

Comments
 (0)