Skip to content

Commit 77ebe50

Browse files
committedFeb 21, 2024·
refactor: strict type checks
1 parent 982a7a9 commit 77ebe50

13 files changed

+83
-66
lines changed
 

‎.github/workflows/ci.yml

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ jobs:
2424
- run: pnpm install
2525
- run: pnpm lint
2626
if: matrix.os == 'ubuntu-latest'
27+
- run: test:types
28+
if: matrix.os == 'ubuntu-latest'
2729
- run: pnpm build
2830
if: matrix.os == 'ubuntu-latest'
2931
- run: pnpm vitest --coverage

‎package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"lint": "eslint --ext .ts,.js src test && prettier -c src test",
2424
"lint:fix": "eslint --ext .ts,.js src test --fix && prettier -w src test",
2525
"release": "pnpm test && pnpm build && changelogen --release && npm publish && git push --follow-tags",
26-
"test": "pnpm lint && vitest run"
26+
"test": "pnpm lint && pnpm test:types && vitest run",
27+
"test:types": "tsc --noEmit"
2728
},
2829
"dependencies": {
2930
"acorn": "^8.11.3",
@@ -45,4 +46,4 @@
4546
"vitest": "^1.2.2"
4647
},
4748
"packageManager": "pnpm@8.15.1"
48-
}
49+
}

‎src/_utils.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,23 @@ import { builtinModules } from "node:module";
22

33
export const BUILTIN_MODULES = new Set(builtinModules);
44

5-
export function normalizeSlash(string_) {
6-
return string_.replace(/\\/g, "/");
5+
export function normalizeSlash(path: string): string {
6+
return path.replace(/\\/g, "/");
77
}
88

9-
export function isObject(value) {
9+
export function isObject(value: unknown): boolean {
1010
return value !== null && typeof value === "object";
1111
}
1212

13-
export function matchAll(regex, string, addition) {
13+
export function matchAll(regex: RegExp, string: string, addition: any) {
1414
const matches = [];
1515
for (const match of string.matchAll(regex)) {
1616
matches.push({
1717
...addition,
1818
...match.groups,
1919
code: match[0],
2020
start: match.index,
21-
end: match.index + match[0].length,
21+
end: (match.index || 0) + match[0].length,
2222
});
2323
}
2424
return matches;

‎src/analyze.ts

+25-26
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,13 @@ export function parseStaticImport(
122122
): ParsedStaticImport {
123123
const cleanedImports = clearImports(matched.imports);
124124

125-
const namedImports = {};
126-
for (const namedImport of cleanedImports
127-
.match(/{([^}]*)}/)?.[1]
128-
?.split(",") || []) {
129-
const [, source = namedImport.trim(), importName = source] =
130-
namedImport.match(/^\s*(\S*) as (\S*)\s*$/) || [];
131-
if (source && !TYPE_RE.test(source)) {
125+
const namedImports: Record<string, string> = {};
126+
const _matches = cleanedImports.match(/{([^}]*)}/)?.[1]?.split(",") || [];
127+
for (const namedImport of _matches) {
128+
const _match = namedImport.match(/^\s*(\S*) as (\S*)\s*$/);
129+
const source = _match?.[1] || namedImport.trim();
130+
const importName = _match?.[2] || source;
131+
if (!TYPE_RE.test(source)) {
132132
namedImports[source] = importName;
133133
}
134134
}
@@ -151,16 +151,14 @@ export function parseTypeImport(
151151

152152
const cleanedImports = clearImports(matched.imports);
153153

154-
const namedImports = {};
155-
for (const namedImport of cleanedImports
156-
.match(/{([^}]*)}/)?.[1]
157-
?.split(",") || []) {
158-
const [, source = namedImport.trim(), importName = source] = (() => {
159-
return /\s+as\s+/.test(namedImport)
160-
? namedImport.match(/^\s*type\s+(\S*) as (\S*)\s*$/) || []
161-
: namedImport.match(/^\s*type\s+(\S*)\s*$/) || [];
162-
})();
163-
154+
const namedImports: Record<string, string> = {};
155+
const _matches = cleanedImports.match(/{([^}]*)}/)?.[1]?.split(",") || [];
156+
for (const namedImport of _matches) {
157+
const _match = /\s+as\s+/.test(namedImport)
158+
? namedImport.match(/^\s*type\s+(\S*) as (\S*)\s*$/)
159+
: namedImport.match(/^\s*type\s+(\S*)\s*$/);
160+
const source = _match?.[1] || namedImport.trim();
161+
const importName = _match?.[2] || source;
164162
if (source && TYPE_RE.test(namedImport)) {
165163
namedImports[source] = importName;
166164
}
@@ -326,7 +324,7 @@ function normalizeExports(exports: (ESMExport & { declaration?: string })[]) {
326324
if (!exp.names && exp.name) {
327325
exp.names = [exp.name];
328326
}
329-
if (exp.type === "declaration") {
327+
if (exp.type === "declaration" && exp.declaration) {
330328
exp.declarationType = exp.declaration.replace(
331329
/^declare\s*/,
332330
"",
@@ -368,14 +366,15 @@ export async function resolveModuleExportNames(
368366

369367
// Recursive * exports
370368
for (const exp of exports) {
371-
if (exp.type === "star") {
372-
const subExports = await resolveModuleExportNames(exp.specifier, {
373-
...options,
374-
url,
375-
});
376-
for (const subExport of subExports) {
377-
exportNames.add(subExport);
378-
}
369+
if (exp.type !== "star" || !exp.specifier) {
370+
continue;
371+
}
372+
const subExports = await resolveModuleExportNames(exp.specifier, {
373+
...options,
374+
url,
375+
});
376+
for (const subExport of subExports) {
377+
exportNames.add(subExport);
379378
}
380379
}
381380

‎src/cjs.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,19 @@ export function createCommonJS(url: string): CommonjsContext {
1616
const __dirname = dirname(__filename);
1717

1818
// Lazy require
19-
let _nativeRequire;
20-
const getNativeRequire = () =>
21-
_nativeRequire || (_nativeRequire = createRequire(url));
22-
function require(id) {
19+
let _nativeRequire: typeof require;
20+
const getNativeRequire = () => {
21+
if (!_nativeRequire) {
22+
_nativeRequire = createRequire(url);
23+
}
24+
return _nativeRequire;
25+
};
26+
function require(id: string): any {
2327
return getNativeRequire()(id);
2428
}
25-
require.resolve = (id, options) => getNativeRequire().resolve(id, options);
26-
29+
require.resolve = function requireResolve(id: string, options: any): string {
30+
return getNativeRequire().resolve(id, options);
31+
};
2732
return {
2833
__filename,
2934
__dirname,

‎src/eval.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export async function evalModule(
3535

3636
export function transformModule(
3737
code: string,
38-
options?: EvaluateOptions,
38+
options: EvaluateOptions = {},
3939
): Promise<string> {
4040
// Convert JSON to module
4141
if (options.url && options.url.endsWith(".json")) {

‎src/resolve.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ function _tryModuleResolve(
2828
): URL | undefined {
2929
try {
3030
return moduleResolve(id, url, conditions);
31-
} catch (error) {
32-
if (!NOT_FOUND_ERRORS.has(error.code)) {
31+
} catch (error: any) {
32+
if (!NOT_FOUND_ERRORS.has(error?.code)) {
3333
throw error;
3434
}
3535
}
@@ -66,8 +66,8 @@ function _resolve(id: string | URL, options: ResolveOptions = {}): string {
6666
if (stat.isFile()) {
6767
return pathToFileURL(id);
6868
}
69-
} catch (error) {
70-
if (error.code !== "ENOENT") {
69+
} catch (error: any) {
70+
if (error?.code !== "ENOENT") {
7171
throw error;
7272
}
7373
}
@@ -80,10 +80,10 @@ function _resolve(id: string | URL, options: ResolveOptions = {}): string {
8080

8181
// Search paths
8282
const _urls: URL[] = (
83-
Array.isArray(options.url) ? options.url : [options.url]
83+
(Array.isArray(options.url) ? options.url : [options.url]) as URL[]
8484
)
8585
.filter(Boolean)
86-
.map((url) => new URL(normalizeid(url!.toString())));
86+
.map((url) => new URL(normalizeid(url.toString())));
8787
if (_urls.length === 0) {
8888
_urls.push(new URL(pathToFileURL(process.cwd())));
8989
}
@@ -226,21 +226,21 @@ function _findSubpath(subpath: string, exports: PackageJson["exports"]) {
226226
subpath = subpath.startsWith("/") ? `.${subpath}` : `./${subpath}`;
227227
}
228228

229-
if (subpath in exports) {
229+
if (subpath in (exports || {})) {
230230
return subpath;
231231
}
232232

233233
return _flattenExports(exports).find((p) => p.fsPath === subpath)?.subpath;
234234
}
235235

236236
function _flattenExports(
237-
exports: Exclude<PackageJson["exports"], string>,
237+
exports: Exclude<PackageJson["exports"], string> = {},
238238
parentSubpath = "./",
239239
): { subpath: string; fsPath: string; condition?: string }[] {
240240
return Object.entries(exports).flatMap(([key, value]) => {
241241
const [subpath, condition] = key.startsWith(".")
242242
? [key.slice(1), undefined]
243-
: [undefined, key];
243+
: ["", key];
244244
const _subPath = joinURL(parentSubpath, subpath);
245245
// eslint-disable-next-line unicorn/prefer-ternary
246246
if (typeof value === "string") {

‎src/syntax.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export async function isValidNodeImport(
8080
const options = { ...validNodeImportDefaults, ..._options };
8181

8282
const proto = getProtocol(id);
83-
if (proto && !options.allowedProtocols.includes(proto)) {
83+
if (proto && !options.allowedProtocols?.includes(proto)) {
8484
return false;
8585
}
8686

‎src/utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,5 @@ const ProtocolRegex = /^(?<proto>.{2,}?):.+$/;
7171

7272
export function getProtocol(id: string): string | undefined {
7373
const proto = id.match(ProtocolRegex);
74-
return proto ? proto.groups.proto : undefined;
74+
return proto ? proto.groups?.proto : undefined;
7575
}

‎test.mjs

-1
This file was deleted.

‎test/imports.test.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,17 @@ import {
99

1010
// -- Static import --
1111

12-
const staticTests = {
12+
type MaybeArray<T> = T | T[];
13+
const staticTests: Record<
14+
string,
15+
MaybeArray<{
16+
specifier: string;
17+
defaultImport?: string;
18+
namespacedImport?: string;
19+
namedImports?: Record<string, string>;
20+
type?: string;
21+
}>
22+
> = {
1323
'import defaultMember from "module-name";': {
1424
specifier: "module-name",
1525
defaultImport: "defaultMember",
@@ -188,7 +198,7 @@ const dynamicTests = {
188198
},
189199
'// import("abc").then(r => r.default)': [],
190200
'/* import("abc").then(r => r.default) */': [],
191-
};
201+
} as const;
192202

193203
const TypeTests = {
194204
'import { type Foo, Bar } from "module-name";': {
@@ -276,7 +286,7 @@ describe("findDynamicImports", () => {
276286
const match = matches[0];
277287
if (match) {
278288
expect(match.type).to.equal("dynamic");
279-
expect(match.expression.trim()).to.equal(test.expression);
289+
expect(match.expression.trim()).to.equal((test as any).expression);
280290
}
281291
});
282292
}

‎test/utils.test.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ describe("isNodeBuiltin", () => {
2020
"fs/fake": true, // invalid import
2121
};
2222

23-
for (const id in cases) {
24-
it(`'${id}': ${cases[id]}`, () => {
25-
expect(isNodeBuiltin(id)).to.equal(cases[id]);
23+
for (const [input, output] of Object.entries(cases)) {
24+
it(`'${input}': ${output}`, () => {
25+
expect(isNodeBuiltin(input)).to.equal(output);
2626
});
2727
}
2828

@@ -43,10 +43,10 @@ describe("sanitizeFilePath", () => {
4343
"Foo.vue&vue&type=script&setup=true&generic=T%20extends%20any%2C%20O%20extends%20T%3CZ%7Ca%3E&lang":
4444
"Foo.vue_vue_type_script_setup_true_generic_T_extends_any__O_extends_T_Z_a__lang",
4545
"": "",
46-
};
47-
for (const id in cases) {
48-
it(`'${id}': ${cases[id]}`, () => {
49-
expect(sanitizeFilePath(id)).to.equal(cases[id]);
46+
} as const;
47+
for (const [input, output] of Object.entries(cases)) {
48+
it(`'${input}': ${output}`, () => {
49+
expect(sanitizeFilePath(input)).to.equal(output);
5050
});
5151
}
5252

‎tsconfig.json

+5-4
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
"moduleResolution": "Node",
66
"resolveJsonModule": true,
77
"esModuleInterop": true,
8-
"checkJs": true,
8+
"strict": true,
9+
"skipLibCheck": true,
910
"paths": {
10-
"mlly": ["./"]
11+
"mlly": ["./"],
1112
},
12-
"types": ["node"]
13-
}
13+
"types": ["node"],
14+
},
1415
}

0 commit comments

Comments
 (0)
Please sign in to comment.