diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index cd566c0e6e..fbb832400c 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -186,6 +186,18 @@ namespace ts.moduleSpecifiers { return result; } + function numberOfDirectorySeparators(str: string) { + const match = str.match(/\//g); + return match ? match.length : 0; + } + + function comparePathsByNumberOfDirectrorySeparators(a: string, b: string) { + return compareValues( + numberOfDirectorySeparators(a), + numberOfDirectorySeparators(b) + ); + } + /** * Looks for existing imports that use symlinks to this module. * Symlinks will be returned first so they are preferred over the real path. @@ -214,7 +226,32 @@ namespace ts.moduleSpecifiers { } }); result.push(...targets); - return result; + if (result.length < 2) return result; + + // Sort by paths closest to importing file Name directory + const allFileNames = arrayToMap(result, identity, getCanonicalFileName); + const sortedPaths: string[] = []; + for ( + let directory = getDirectoryPath(toPath(importingFileName, cwd, getCanonicalFileName)); + allFileNames.size !== 0; + directory = getDirectoryPath(directory) + ) { + const directoryStart = ensureTrailingDirectorySeparator(directory); + let pathsInDirectory: string[] | undefined; + allFileNames.forEach((canonicalFileName, fileName) => { + if (startsWith(canonicalFileName, directoryStart)) { + (pathsInDirectory || (pathsInDirectory = [])).push(fileName); + allFileNames.delete(fileName); + } + }); + if (pathsInDirectory) { + if (pathsInDirectory.length > 1) { + pathsInDirectory.sort(comparePathsByNumberOfDirectrorySeparators); + } + sortedPaths.push(...pathsInDirectory); + } + } + return sortedPaths; } function tryGetModuleNameFromAmbientModule(moduleSymbol: Symbol): string | undefined { diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 06871fc9ca..3ee30ae28c 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -131,7 +131,11 @@ namespace ts { export function listFiles(program: ProgramToEmitFilesAndReportErrors, writeFileName: (s: string) => void) { if (program.getCompilerOptions().listFiles) { forEach(program.getSourceFiles(), file => { - writeFileName(file.fileName); + writeFileName( + !file.redirectInfo ? + file.fileName : + `${file.fileName} -> ${file.redirectInfo.redirectTarget.fileName}` + ); }); } } diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 14544ce0bc..49272d6c1b 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -109,6 +109,7 @@ "unittests/tsbuild/transitiveReferences.ts", "unittests/tsbuild/watchEnvironment.ts", "unittests/tsbuild/watchMode.ts", + "unittests/tsc/declarationEmit.ts", "unittests/tscWatch/consoleClearing.ts", "unittests/tscWatch/emit.ts", "unittests/tscWatch/emitAndErrorUpdates.ts", diff --git a/src/testRunner/unittests/tsc/declarationEmit.ts b/src/testRunner/unittests/tsc/declarationEmit.ts new file mode 100644 index 0000000000..5577ad2202 --- /dev/null +++ b/src/testRunner/unittests/tsc/declarationEmit.ts @@ -0,0 +1,136 @@ +namespace ts { + describe("unittests:: tsc:: declarationEmit::", () => { + verifyTsc({ + scenario: "declarationEmit", + subScenario: "when same version is referenced through source and another symlinked package", + fs: () => { + const fsaPackageJson = utils.dedent` + { + "name": "typescript-fsa", + "version": "3.0.0-beta-2" + }`; + const fsaIndex = utils.dedent` + export interface Action { + type: string; + payload: Payload; + } + export declare type ActionCreator = { + type: string; + (payload: Payload): Action; + } + export interface ActionCreatorFactory { + (type: string): ActionCreator; + } + export declare function actionCreatorFactory(prefix?: string | null): ActionCreatorFactory; + export default actionCreatorFactory;`; + return loadProjectFromFiles({ + "/src/plugin-two/index.d.ts": utils.dedent` + declare const _default: { + features: { + featureOne: { + actions: { + featureOne: { + (payload: { + name: string; + order: number; + }, meta?: { + [key: string]: any; + }): import("typescript-fsa").Action<{ + name: string; + order: number; + }>; + }; + }; + path: string; + }; + }; + }; + export default _default;`, + "/src/plugin-two/node_modules/typescript-fsa/package.json": fsaPackageJson, + "/src/plugin-two/node_modules/typescript-fsa/index.d.ts": fsaIndex, + "/src/plugin-one/tsconfig.json": utils.dedent` + { + "compilerOptions": { + "target": "es5", + "declaration": true, + }, + }`, + "/src/plugin-one/index.ts": utils.dedent` + import pluginTwo from "plugin-two"; // include this to add reference to symlink`, + "/src/plugin-one/action.ts": utils.dedent` + import { actionCreatorFactory } from "typescript-fsa"; // Include version of shared lib + const action = actionCreatorFactory("somekey"); + const featureOne = action<{ route: string }>("feature-one"); + export const actions = { featureOne };`, + "/src/plugin-one/node_modules/typescript-fsa/package.json": fsaPackageJson, + "/src/plugin-one/node_modules/typescript-fsa/index.d.ts": fsaIndex, + "/src/plugin-one/node_modules/plugin-two": new vfs.Symlink("/src/plugin-two"), + }); + }, + commandLineArgs: ["-p", "src/plugin-one", "--listFiles"] + }); + + verifyTsc({ + scenario: "declarationEmit", + subScenario: "when pkg references sibling package through indirect symlink", + fs: () => loadProjectFromFiles({ + "/src/pkg1/dist/index.d.ts": utils.dedent` + export * from './types';`, + "/src/pkg1/dist/types.d.ts": utils.dedent` + export declare type A = { + id: string; + }; + export declare type B = { + id: number; + }; + export declare type IdType = A | B; + export declare class MetadataAccessor { + readonly key: string; + private constructor(); + toString(): string; + static create(key: string): MetadataAccessor; + }`, + "/src/pkg1/package.json": utils.dedent` + { + "name": "@raymondfeng/pkg1", + "version": "1.0.0", + "description": "", + "main": "dist/index.js", + "typings": "dist/index.d.ts" + }`, + "/src/pkg2/dist/index.d.ts": utils.dedent` + export * from './types';`, + "/src/pkg2/dist/types.d.ts": utils.dedent` + export {MetadataAccessor} from '@raymondfeng/pkg1';`, + "/src/pkg2/package.json": utils.dedent` + { + "name": "@raymondfeng/pkg2", + "version": "1.0.0", + "description": "", + "main": "dist/index.js", + "typings": "dist/index.d.ts" + }`, + "/src/pkg3/src/index.ts": utils.dedent` + export * from './keys';`, + "/src/pkg3/src/keys.ts": utils.dedent` + import {MetadataAccessor} from "@raymondfeng/pkg2"; + export const ADMIN = MetadataAccessor.create('1');`, + "/src/pkg3/tsconfig.json": utils.dedent` + { + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "target": "es5", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "declaration": true + } + }`, + "/src/pkg2/node_modules/@raymondfeng/pkg1": new vfs.Symlink("/src/pkg1"), + "/src/pkg3/node_modules/@raymondfeng/pkg2": new vfs.Symlink("/src/pkg2"), + }), + commandLineArgs: ["-p", "src/pkg3", "--listFiles"] + }); + }); +} diff --git a/tests/baselines/reference/declarationEmitReexportedSymlinkReference3.errors.txt b/tests/baselines/reference/declarationEmitReexportedSymlinkReference3.errors.txt deleted file mode 100644 index 1981ce63c9..0000000000 --- a/tests/baselines/reference/declarationEmitReexportedSymlinkReference3.errors.txt +++ /dev/null @@ -1,60 +0,0 @@ -tests/cases/compiler/monorepo/pkg3/src/keys.ts(3,14): error TS2742: The inferred type of 'ADMIN' cannot be named without a reference to '../../pkg2/node_modules/@raymondfeng/pkg1/dist'. This is likely not portable. A type annotation is necessary. - - -==== tests/cases/compiler/monorepo/pkg3/tsconfig.json (0 errors) ==== - { - "compilerOptions": { - "outDir": "dist", - "rootDir": "src", - "target": "es5", - "module": "commonjs", - "strict": true, - "esModuleInterop": true, - "declaration": true - } - } - -==== tests/cases/compiler/monorepo/pkg1/dist/index.d.ts (0 errors) ==== - export * from './types'; -==== tests/cases/compiler/monorepo/pkg1/dist/types.d.ts (0 errors) ==== - export declare type A = { - id: string; - }; - export declare type B = { - id: number; - }; - export declare type IdType = A | B; - export declare class MetadataAccessor { - readonly key: string; - private constructor(); - toString(): string; - static create(key: string): MetadataAccessor; - } -==== tests/cases/compiler/monorepo/pkg1/package.json (0 errors) ==== - { - "name": "@raymondfeng/pkg1", - "version": "1.0.0", - "description": "", - "main": "dist/index.js", - "typings": "dist/index.d.ts" - } -==== tests/cases/compiler/monorepo/pkg2/dist/index.d.ts (0 errors) ==== - export * from './types'; -==== tests/cases/compiler/monorepo/pkg2/dist/types.d.ts (0 errors) ==== - export {MetadataAccessor} from '@raymondfeng/pkg1'; -==== tests/cases/compiler/monorepo/pkg2/package.json (0 errors) ==== - { - "name": "@raymondfeng/pkg2", - "version": "1.0.0", - "description": "", - "main": "dist/index.js", - "typings": "dist/index.d.ts" - } -==== tests/cases/compiler/monorepo/pkg3/src/index.ts (0 errors) ==== - export * from './keys'; -==== tests/cases/compiler/monorepo/pkg3/src/keys.ts (1 errors) ==== - import {MetadataAccessor} from "@raymondfeng/pkg2"; - - export const ADMIN = MetadataAccessor.create('1'); - ~~~~~ -!!! error TS2742: The inferred type of 'ADMIN' cannot be named without a reference to '../../pkg2/node_modules/@raymondfeng/pkg1/dist'. This is likely not portable. A type annotation is necessary. \ No newline at end of file diff --git a/tests/baselines/reference/declarationEmitReexportedSymlinkReference3.js b/tests/baselines/reference/declarationEmitReexportedSymlinkReference3.js index 2ac5a9fffb..46b3eded63 100644 --- a/tests/baselines/reference/declarationEmitReexportedSymlinkReference3.js +++ b/tests/baselines/reference/declarationEmitReexportedSymlinkReference3.js @@ -57,5 +57,8 @@ Object.defineProperty(exports, "__esModule", { value: true }); __export(require("./keys")); +//// [keys.d.ts] +import { MetadataAccessor } from "@raymondfeng/pkg2"; +export declare const ADMIN: MetadataAccessor; //// [index.d.ts] export * from './keys'; diff --git a/tests/baselines/reference/tsc/declarationEmit/initial-build/when-pkg-references-sibling-package-through-indirect-symlink.js b/tests/baselines/reference/tsc/declarationEmit/initial-build/when-pkg-references-sibling-package-through-indirect-symlink.js new file mode 100644 index 0000000000..42cb6cf78c --- /dev/null +++ b/tests/baselines/reference/tsc/declarationEmit/initial-build/when-pkg-references-sibling-package-through-indirect-symlink.js @@ -0,0 +1,33 @@ +//// [/lib/initial-buildOutput.txt] +/lib/tsc -p src/pkg3 --listFiles +src/pkg3/src/keys.ts(2,14): error TS2742: The inferred type of 'ADMIN' cannot be named without a reference to '@raymondfeng/pkg2/node_modules/@raymondfeng/pkg1'. This is likely not portable. A type annotation is necessary. +/lib/lib.d.ts +/src/pkg3/node_modules/@raymondfeng/pkg2/node_modules/@raymondfeng/pkg1/dist/types.d.ts +/src/pkg3/node_modules/@raymondfeng/pkg2/node_modules/@raymondfeng/pkg1/dist/index.d.ts +/src/pkg3/node_modules/@raymondfeng/pkg2/dist/types.d.ts +/src/pkg3/node_modules/@raymondfeng/pkg2/dist/index.d.ts +/src/pkg3/src/keys.ts +/src/pkg3/src/index.ts +exitCode:: 1 + + +//// [/src/pkg3/dist/index.d.ts] +export * from './keys'; + + +//// [/src/pkg3/dist/index.js] +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +__export(require("./keys")); + + +//// [/src/pkg3/dist/keys.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var pkg2_1 = require("@raymondfeng/pkg2"); +exports.ADMIN = pkg2_1.MetadataAccessor.create('1'); + + diff --git a/tests/baselines/reference/tsc/declarationEmit/initial-build/when-same-version-is-referenced-through-source-and-another-symlinked-package.js b/tests/baselines/reference/tsc/declarationEmit/initial-build/when-same-version-is-referenced-through-source-and-another-symlinked-package.js new file mode 100644 index 0000000000..910b9d1db3 --- /dev/null +++ b/tests/baselines/reference/tsc/declarationEmit/initial-build/when-same-version-is-referenced-through-source-and-another-symlinked-package.js @@ -0,0 +1,37 @@ +//// [/lib/initial-buildOutput.txt] +/lib/tsc -p src/plugin-one --listFiles +/lib/lib.d.ts +/src/plugin-one/node_modules/typescript-fsa/index.d.ts +/src/plugin-one/action.ts +/src/plugin-one/node_modules/plugin-two/node_modules/typescript-fsa/index.d.ts -> /src/plugin-one/node_modules/typescript-fsa/index.d.ts +/src/plugin-one/node_modules/plugin-two/index.d.ts +/src/plugin-one/index.ts +exitCode:: 0 + + +//// [/src/plugin-one/action.d.ts] +export declare const actions: { + featureOne: import("typescript-fsa").ActionCreator<{ + route: string; + }>; +}; + + +//// [/src/plugin-one/action.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var typescript_fsa_1 = require("typescript-fsa"); // Include version of shared lib +var action = typescript_fsa_1.actionCreatorFactory("somekey"); +var featureOne = action("feature-one"); +exports.actions = { featureOne: featureOne }; + + +//// [/src/plugin-one/index.d.ts] +export {}; + + +//// [/src/plugin-one/index.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); + +