Skip to content
This repository has been archived by the owner on Oct 18, 2023. It is now read-only.

fix: stricter directory import #17

Merged
merged 7 commits into from
May 25, 2022
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
74 changes: 53 additions & 21 deletions src/loaders.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import path from 'path';
import {
transform,
installSourceMapSupport,
Expand All @@ -23,22 +24,63 @@ type Resolved = {
format: ModuleFormat;
};

type Context = {
conditions: string[];
parentURL: string | undefined;
};

type resolve = (
specifier: string,
context: {
conditions: string[];
parentURL: string | undefined;
},
context: Context,
defaultResolve: resolve,
) => MaybePromise<Resolved>;

const hasExtensionPattern = /\.\w+$/;

const extensions = ['.js', '.json', '.ts', '.tsx', '.jsx'] as const;
const possibleSuffixes = [
...extensions,
...extensions.map(extension => `/index${extension}` as const),
];

async function tryExtensions(
specifier: string,
context: Context,
defaultResolve: resolve,
) {
let error;
for (const extension of extensions) {
try {
return await resolve(
specifier + extension,
context,
defaultResolve,
);
} catch (_error: any) {
if (error === undefined) {
const { message } = _error;
_error.message = _error.message.replace(`${extension}'`, "'");
_error.stack = _error.stack.replace(message, _error.message);
error = _error;
}
}
}

throw error;
}

async function tryDirectory(
specifier: string,
context: Context,
defaultResolve: resolve,
) {
const appendIndex = specifier.endsWith('/') ? 'index' : `${path.sep}index`;

try {
return await tryExtensions(specifier + appendIndex, context, defaultResolve);
} catch (error: any) {
const { message } = error;
error.message = error.message.replace(`${appendIndex}'`, "'");
error.stack = error.stack.replace(message, error.message);
throw error;
}
}

export const resolve: resolve = async function (
specifier,
Expand All @@ -53,7 +95,7 @@ export const resolve: resolve = async function (

// If directory, can be index.js, index.ts, etc.
if (specifier.endsWith('/')) {
return resolve(`${specifier}index`, context, defaultResolve);
return await tryDirectory(specifier, context, defaultResolve);
}

/**
Expand All @@ -79,24 +121,14 @@ export const resolve: resolve = async function (
} catch (error) {
if (error instanceof Error) {
if ((error as any).code === 'ERR_UNSUPPORTED_DIR_IMPORT') {
return resolve(`${specifier}/index`, context, defaultResolve);
return await tryDirectory(specifier, context, defaultResolve);
}

if (
(error as any).code === 'ERR_MODULE_NOT_FOUND'
&& !hasExtensionPattern.test(specifier)
) {
for (const suffix of possibleSuffixes) {
try {
const trySpecifier = specifier + (
specifier.endsWith('/') && suffix.startsWith('/')
? suffix.slice(1)
: suffix
);

return await resolve(trySpecifier, context, defaultResolve);
} catch {}
}
return await tryExtensions(specifier, context, defaultResolve);
}
}

Expand Down
12 changes: 12 additions & 0 deletions tests/specs/typescript/cts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { testSuite, expect } from 'manten';
import type { NodeApis } from '../../utils/node-with-loader';

const isWin = process.platform === 'win32';

export default testSuite(async ({ describe }, node: NodeApis) => {
describe('.cts extension', ({ describe }) => {
describe('full path', ({ test }) => {
Expand Down Expand Up @@ -49,6 +51,11 @@ export default testSuite(async ({ describe }, node: NodeApis) => {
test('Import', async () => {
const nodeProcess = await node.import(importPath);
expect(nodeProcess.stderr).toMatch('Cannot find module');
expect(nodeProcess.stderr).toMatch(
isWin
? '\\lib\\ts-ext-cts\\index\''
: '/lib/ts-ext-cts/index\'',
);
});
});

Expand All @@ -63,6 +70,11 @@ export default testSuite(async ({ describe }, node: NodeApis) => {
test('Import', async () => {
const nodeProcess = await node.import(importPath);
expect(nodeProcess.stderr).toMatch('Cannot find module');
expect(nodeProcess.stderr).toMatch(
isWin
? '\\lib\\ts-ext-cts\''
: '/lib/ts-ext-cts\'',
);
});
});
});
Expand Down
14 changes: 13 additions & 1 deletion tests/specs/typescript/mts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { testSuite, expect } from 'manten';
import type { NodeApis } from '../../utils/node-with-loader';

const isWin = process.platform === 'win32';

export default testSuite(async ({ describe }, node: NodeApis) => {
describe('.mts extension', ({ describe }) => {
const output = 'loaded ts-ext-mts/index.mts true true true';
Expand Down Expand Up @@ -44,11 +46,16 @@ export default testSuite(async ({ describe }, node: NodeApis) => {
test('Import', async () => {
const nodeProcess = await node.import(importPath);
expect(nodeProcess.stderr).toMatch('Cannot find module');
expect(nodeProcess.stderr).toMatch(
isWin
? '\\lib\\ts-ext-mts\\index\''
: '/lib/ts-ext-mts/index\'',
);
});
});

describe('directory - should not work', ({ test }) => {
const importPath = './lib/ts-ext-mts';
const importPath = './lib/ts-ext-mts/';

test('Load', async () => {
const nodeProcess = await node.load(importPath);
Expand All @@ -58,6 +65,11 @@ export default testSuite(async ({ describe }, node: NodeApis) => {
test('Import', async () => {
const nodeProcess = await node.import(importPath);
expect(nodeProcess.stderr).toMatch('Cannot find module');
expect(nodeProcess.stderr).toMatch(
isWin
? '\\lib\\ts-ext-mts\\\''
: '/lib/ts-ext-mts/\'',
);
});
});
});
Expand Down