Skip to content

Commit

Permalink
Resolve JS files referenced by TypeScript declaration files if possib…
Browse files Browse the repository at this point in the history
…le (#503)

* Add test & fixtures for dts files used with base-url implicit relative scenario

* Resolve TypeScript declaration file to matching js files if possible

* Include other dts extensions

* Add tsconfig file

* Fix tests

* Format changes

---------

Co-authored-by: Lars Kappert <[email protected]>
  • Loading branch information
junyi and webpro authored Mar 12, 2024
1 parent 6906c63 commit 11d1555
Show file tree
Hide file tree
Showing 16 changed files with 90 additions and 13 deletions.
3 changes: 3 additions & 0 deletions packages/eslint-config/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"compilerOptions": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "@fixtures/dts-baseurl-implicit-relative"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const h: () => number;
export default h;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const h = () => {
return 1;
};
export default h;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import h from 'dir/subdir';
import { g } from 'utils/fn';

h;
g;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const g: () => void;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const g = () => {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"compilerOptions": {
"baseUrl": "src"
}
}
4 changes: 2 additions & 2 deletions packages/knip/scripts/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"compilerOptions": {
"esModuleInterop": true,
"module": "NodeNext",
"moduleResolution": "NodeNext",
"moduleResolution": "NodeNext"
},
"include": ["**/*.ts"],
"include": ["**/*.ts"]
}
37 changes: 34 additions & 3 deletions packages/knip/src/typescript/resolveModuleNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { existsSync } from 'node:fs';
import { isBuiltin } from 'node:module';
import ts from 'typescript';
import { sanitizeSpecifier } from '../util/modules.js';
import { dirname, extname, isAbsolute, isInternal, join } from '../util/path.js';
import { basename, dirname, extname, format, isAbsolute, isInternal, join } from '../util/path.js';
import { isDeclarationFileExtension } from './ast-helpers.js';
import { ensureRealFilePath, isVirtualFilePath } from './utils.js';

Expand All @@ -20,6 +20,31 @@ const fileExists = (name: string, containingFile: string) => {
}
};

const DECLARATION_EXTENSIONS_MAP = {
[ts.Extension.Dts]: ts.Extension.Js,
[ts.Extension.Dmts]: ts.Extension.Mjs,
[ts.Extension.Dcts]: ts.Extension.Cjs,
} as const;

const jsMatchingDeclarationFileExists = (resolveDtsFileName: string, declarationFileExtension: string) => {
const mappedExtension =
DECLARATION_EXTENSIONS_MAP[declarationFileExtension as keyof typeof DECLARATION_EXTENSIONS_MAP];
const resolvedFileName = format({
ext: mappedExtension,
dir: dirname(resolveDtsFileName),
name: basename(resolveDtsFileName, declarationFileExtension),
});

if (existsSync(resolvedFileName)) {
return {
resolvedFileName,
extension: mappedExtension,
isExternalLibraryImport: false,
resolvedUsingTsExtension: false,
};
}
};

export function createCustomModuleResolver(
customSys: typeof ts.sys,
compilerOptions: ts.CompilerOptions,
Expand Down Expand Up @@ -66,8 +91,14 @@ export function createCustomModuleResolver(
isDeclarationFileExtension(tsResolvedModule?.extension) &&
isInternal(tsResolvedModule.resolvedFileName)
) {
const module = fileExists(sanitizedSpecifier, containingFile);
if (module) return module;
{
const module = jsMatchingDeclarationFileExists(tsResolvedModule.resolvedFileName, tsResolvedModule.extension);
if (module) return module;
}
{
const module = fileExists(sanitizedSpecifier, containingFile);
if (module) return module;
}
}

if (virtualFileExtensions.length === 0) return tsResolvedModule;
Expand Down
2 changes: 2 additions & 0 deletions packages/knip/src/util/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const extname = path.posix.extname;

export const basename = path.posix.basename;

export const format = path.posix.format;

export const join = path.posix.join;

export const toPosix = (value: string) => value.split(path.sep).join(path.posix.sep);
Expand Down
4 changes: 3 additions & 1 deletion packages/knip/src/util/tsconfig-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { isFile } from './fs.js';
import { FAKE_PATH } from './loader.js';
import { dirname } from './path.js';

const dtsMatch = /\.d\.(c|m)?ts$/;

export const loadTSConfig = async (tsConfigFilePath: string) => {
if (tsConfigFilePath !== FAKE_PATH && isFile(tsConfigFilePath)) {
const config = ts.readConfigFile(tsConfigFilePath, ts.sys.readFile);
const parsedConfig = ts.parseJsonConfigFileContent(config.config, ts.sys, dirname(tsConfigFilePath));
const compilerOptions = parsedConfig.options ?? {};
const definitionPaths = parsedConfig.fileNames.filter(filePath => filePath.endsWith('.d.ts'));
const definitionPaths = parsedConfig.fileNames.filter(filePath => dtsMatch.test(filePath));
return { compilerOptions, definitionPaths };
}
return { compilerOptions: {}, definitionPaths: [] };
Expand Down
21 changes: 21 additions & 0 deletions packages/knip/test/dts-baseurl-implicit-relative.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { main } from '../src/index.js';
import { resolve } from '../src/util/path.js';
import baseArguments from './helpers/baseArguments.js';
import baseCounters from './helpers/baseCounters.js';

const cwd = resolve('fixtures/dts-baseurl-implicit-relative');

test('Include js files referred by the declaration files', async () => {
const { counters } = await main({
...baseArguments,
cwd,
});

assert.deepEqual(counters, {
...baseCounters,
processed: 5,
total: 5,
});
});
3 changes: 0 additions & 3 deletions packages/knip/test/subpath-imports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ test('Allows subpath-imports', async () => {
assert.deepEqual(counters, {
...baseCounters,
dependencies: 1,
files: 1,
processed: 3,
total: 3,
});
Expand All @@ -36,7 +35,6 @@ test('Allows subpath-imports (production)', async () => {
assert.deepEqual(counters, {
...baseCounters,
dependencies: 1,
files: 1,
processed: 3,
total: 3,
});
Expand All @@ -55,7 +53,6 @@ test('Allows subpath-imports (strict)', async () => {
assert.deepEqual(counters, {
...baseCounters,
dependencies: 1,
files: 1,
processed: 3,
total: 3,
});
Expand Down
4 changes: 2 additions & 2 deletions packages/knip/test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"allowJs": true,
"rootDir": ".",
"baseUrl": ".",
"jsx": "preserve",
"jsx": "preserve"
},
"include": [".", "../src"],
"include": [".", "../src"]
}
4 changes: 2 additions & 2 deletions packages/knip/test/workspaces-self-reference.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ test('Find unused files, dependencies and exports in workspaces with cross self-

assert.deepEqual(counters, {
...baseCounters,
processed: 4,
total: 4,
processed: 8,
total: 8,
});
});

0 comments on commit 11d1555

Please sign in to comment.