Skip to content

Commit

Permalink
refactor: strict type checks
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Feb 21, 2024
1 parent 982a7a9 commit 77ebe50
Show file tree
Hide file tree
Showing 13 changed files with 83 additions and 66 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Expand Up @@ -24,6 +24,8 @@ jobs:
- run: pnpm install
- run: pnpm lint
if: matrix.os == 'ubuntu-latest'
- run: test:types
if: matrix.os == 'ubuntu-latest'
- run: pnpm build
if: matrix.os == 'ubuntu-latest'
- run: pnpm vitest --coverage
Expand Down
5 changes: 3 additions & 2 deletions package.json
Expand Up @@ -23,7 +23,8 @@
"lint": "eslint --ext .ts,.js src test && prettier -c src test",
"lint:fix": "eslint --ext .ts,.js src test --fix && prettier -w src test",
"release": "pnpm test && pnpm build && changelogen --release && npm publish && git push --follow-tags",
"test": "pnpm lint && vitest run"
"test": "pnpm lint && pnpm test:types && vitest run",
"test:types": "tsc --noEmit"
},
"dependencies": {
"acorn": "^8.11.3",
Expand All @@ -45,4 +46,4 @@
"vitest": "^1.2.2"
},
"packageManager": "pnpm@8.15.1"
}
}
10 changes: 5 additions & 5 deletions src/_utils.ts
Expand Up @@ -2,23 +2,23 @@ import { builtinModules } from "node:module";

export const BUILTIN_MODULES = new Set(builtinModules);

export function normalizeSlash(string_) {
return string_.replace(/\\/g, "/");
export function normalizeSlash(path: string): string {
return path.replace(/\\/g, "/");
}

export function isObject(value) {
export function isObject(value: unknown): boolean {
return value !== null && typeof value === "object";
}

export function matchAll(regex, string, addition) {
export function matchAll(regex: RegExp, string: string, addition: any) {
const matches = [];
for (const match of string.matchAll(regex)) {
matches.push({
...addition,
...match.groups,
code: match[0],
start: match.index,
end: match.index + match[0].length,
end: (match.index || 0) + match[0].length,
});
}
return matches;
Expand Down
51 changes: 25 additions & 26 deletions src/analyze.ts
Expand Up @@ -122,13 +122,13 @@ export function parseStaticImport(
): ParsedStaticImport {
const cleanedImports = clearImports(matched.imports);

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

const cleanedImports = clearImports(matched.imports);

const namedImports = {};
for (const namedImport of cleanedImports
.match(/{([^}]*)}/)?.[1]
?.split(",") || []) {
const [, source = namedImport.trim(), importName = source] = (() => {
return /\s+as\s+/.test(namedImport)
? namedImport.match(/^\s*type\s+(\S*) as (\S*)\s*$/) || []
: namedImport.match(/^\s*type\s+(\S*)\s*$/) || [];
})();

const namedImports: Record<string, string> = {};
const _matches = cleanedImports.match(/{([^}]*)}/)?.[1]?.split(",") || [];
for (const namedImport of _matches) {
const _match = /\s+as\s+/.test(namedImport)
? namedImport.match(/^\s*type\s+(\S*) as (\S*)\s*$/)
: namedImport.match(/^\s*type\s+(\S*)\s*$/);
const source = _match?.[1] || namedImport.trim();
const importName = _match?.[2] || source;
if (source && TYPE_RE.test(namedImport)) {
namedImports[source] = importName;
}
Expand Down Expand Up @@ -326,7 +324,7 @@ function normalizeExports(exports: (ESMExport & { declaration?: string })[]) {
if (!exp.names && exp.name) {
exp.names = [exp.name];
}
if (exp.type === "declaration") {
if (exp.type === "declaration" && exp.declaration) {
exp.declarationType = exp.declaration.replace(
/^declare\s*/,
"",
Expand Down Expand Up @@ -368,14 +366,15 @@ export async function resolveModuleExportNames(

// Recursive * exports
for (const exp of exports) {
if (exp.type === "star") {
const subExports = await resolveModuleExportNames(exp.specifier, {
...options,
url,
});
for (const subExport of subExports) {
exportNames.add(subExport);
}
if (exp.type !== "star" || !exp.specifier) {
continue;
}
const subExports = await resolveModuleExportNames(exp.specifier, {
...options,
url,
});
for (const subExport of subExports) {
exportNames.add(subExport);
}
}

Expand Down
17 changes: 11 additions & 6 deletions src/cjs.ts
Expand Up @@ -16,14 +16,19 @@ export function createCommonJS(url: string): CommonjsContext {
const __dirname = dirname(__filename);

// Lazy require
let _nativeRequire;
const getNativeRequire = () =>
_nativeRequire || (_nativeRequire = createRequire(url));
function require(id) {
let _nativeRequire: typeof require;
const getNativeRequire = () => {
if (!_nativeRequire) {
_nativeRequire = createRequire(url);
}
return _nativeRequire;
};
function require(id: string): any {
return getNativeRequire()(id);
}
require.resolve = (id, options) => getNativeRequire().resolve(id, options);

require.resolve = function requireResolve(id: string, options: any): string {
return getNativeRequire().resolve(id, options);
};
return {
__filename,
__dirname,
Expand Down
2 changes: 1 addition & 1 deletion src/eval.ts
Expand Up @@ -35,7 +35,7 @@ export async function evalModule(

export function transformModule(
code: string,
options?: EvaluateOptions,
options: EvaluateOptions = {},
): Promise<string> {
// Convert JSON to module
if (options.url && options.url.endsWith(".json")) {
Expand Down
18 changes: 9 additions & 9 deletions src/resolve.ts
Expand Up @@ -28,8 +28,8 @@ function _tryModuleResolve(
): URL | undefined {
try {
return moduleResolve(id, url, conditions);
} catch (error) {
if (!NOT_FOUND_ERRORS.has(error.code)) {
} catch (error: any) {
if (!NOT_FOUND_ERRORS.has(error?.code)) {
throw error;
}
}
Expand Down Expand Up @@ -66,8 +66,8 @@ function _resolve(id: string | URL, options: ResolveOptions = {}): string {
if (stat.isFile()) {
return pathToFileURL(id);
}
} catch (error) {
if (error.code !== "ENOENT") {
} catch (error: any) {
if (error?.code !== "ENOENT") {
throw error;
}
}
Expand All @@ -80,10 +80,10 @@ function _resolve(id: string | URL, options: ResolveOptions = {}): string {

// Search paths
const _urls: URL[] = (
Array.isArray(options.url) ? options.url : [options.url]
(Array.isArray(options.url) ? options.url : [options.url]) as URL[]
)
.filter(Boolean)
.map((url) => new URL(normalizeid(url!.toString())));
.map((url) => new URL(normalizeid(url.toString())));
if (_urls.length === 0) {
_urls.push(new URL(pathToFileURL(process.cwd())));
}
Expand Down Expand Up @@ -226,21 +226,21 @@ function _findSubpath(subpath: string, exports: PackageJson["exports"]) {
subpath = subpath.startsWith("/") ? `.${subpath}` : `./${subpath}`;
}

if (subpath in exports) {
if (subpath in (exports || {})) {
return subpath;
}

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

function _flattenExports(
exports: Exclude<PackageJson["exports"], string>,
exports: Exclude<PackageJson["exports"], string> = {},
parentSubpath = "./",
): { subpath: string; fsPath: string; condition?: string }[] {
return Object.entries(exports).flatMap(([key, value]) => {
const [subpath, condition] = key.startsWith(".")
? [key.slice(1), undefined]
: [undefined, key];
: ["", key];
const _subPath = joinURL(parentSubpath, subpath);
// eslint-disable-next-line unicorn/prefer-ternary
if (typeof value === "string") {
Expand Down
2 changes: 1 addition & 1 deletion src/syntax.ts
Expand Up @@ -80,7 +80,7 @@ export async function isValidNodeImport(
const options = { ...validNodeImportDefaults, ..._options };

const proto = getProtocol(id);
if (proto && !options.allowedProtocols.includes(proto)) {
if (proto && !options.allowedProtocols?.includes(proto)) {
return false;
}

Expand Down
2 changes: 1 addition & 1 deletion src/utils.ts
Expand Up @@ -71,5 +71,5 @@ const ProtocolRegex = /^(?<proto>.{2,}?):.+$/;

export function getProtocol(id: string): string | undefined {
const proto = id.match(ProtocolRegex);
return proto ? proto.groups.proto : undefined;
return proto ? proto.groups?.proto : undefined;
}
1 change: 0 additions & 1 deletion test.mjs

This file was deleted.

16 changes: 13 additions & 3 deletions test/imports.test.ts
Expand Up @@ -9,7 +9,17 @@ import {

// -- Static import --

const staticTests = {
type MaybeArray<T> = T | T[];
const staticTests: Record<
string,
MaybeArray<{
specifier: string;
defaultImport?: string;
namespacedImport?: string;
namedImports?: Record<string, string>;
type?: string;
}>
> = {
'import defaultMember from "module-name";': {
specifier: "module-name",
defaultImport: "defaultMember",
Expand Down Expand Up @@ -188,7 +198,7 @@ const dynamicTests = {
},
'// import("abc").then(r => r.default)': [],
'/* import("abc").then(r => r.default) */': [],
};
} as const;

const TypeTests = {
'import { type Foo, Bar } from "module-name";': {
Expand Down Expand Up @@ -276,7 +286,7 @@ describe("findDynamicImports", () => {
const match = matches[0];
if (match) {
expect(match.type).to.equal("dynamic");
expect(match.expression.trim()).to.equal(test.expression);
expect(match.expression.trim()).to.equal((test as any).expression);
}
});
}
Expand Down
14 changes: 7 additions & 7 deletions test/utils.test.ts
Expand Up @@ -20,9 +20,9 @@ describe("isNodeBuiltin", () => {
"fs/fake": true, // invalid import
};

for (const id in cases) {
it(`'${id}': ${cases[id]}`, () => {
expect(isNodeBuiltin(id)).to.equal(cases[id]);
for (const [input, output] of Object.entries(cases)) {
it(`'${input}': ${output}`, () => {
expect(isNodeBuiltin(input)).to.equal(output);
});
}

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

Expand Down
9 changes: 5 additions & 4 deletions tsconfig.json
Expand Up @@ -5,10 +5,11 @@
"moduleResolution": "Node",
"resolveJsonModule": true,
"esModuleInterop": true,
"checkJs": true,
"strict": true,
"skipLibCheck": true,
"paths": {
"mlly": ["./"]
"mlly": ["./"],
},
"types": ["node"]
}
"types": ["node"],
},
}

0 comments on commit 77ebe50

Please sign in to comment.