From b24b5c4ec190d4ff16e72810f93a22db41df51ba Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 27 Mar 2024 11:45:57 -0700 Subject: [PATCH] Allow cross-project references to const enums in isolatedModules when referenced project has preserveConstEnums (#57914) --- src/compiler/checker.ts | 3 +- src/compiler/program.ts | 1 + src/compiler/types.ts | 8 +- .../unittests/tsc/projectReferences.ts | 27 ++ .../unittests/tsserver/projectReferences.ts | 29 ++ ...erenced-project-with-preserveConstEnums.js | 61 ++++ ...erenced-project-with-preserveConstEnums.js | 337 ++++++++++++++++++ 7 files changed, 464 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/tsc/projectReferences/referencing-ambient-const-enum-from-referenced-project-with-preserveConstEnums.js create mode 100644 tests/baselines/reference/tsserver/projectReferences/referencing-const-enum-from-referenced-project-with-preserveConstEnums.js diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 53bec2e0935ae..816a86fb50cf2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -39782,7 +39782,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (getIsolatedModules(compilerOptions)) { Debug.assert(!!(type.symbol.flags & SymbolFlags.ConstEnum)); const constEnumDeclaration = type.symbol.valueDeclaration as EnumDeclaration; - if (constEnumDeclaration.flags & NodeFlags.Ambient && !isValidTypeOnlyAliasUseSite(node)) { + const redirect = host.getRedirectReferenceForResolutionFromSourceOfProject(getSourceFileOfNode(constEnumDeclaration).resolvedPath); + if (constEnumDeclaration.flags & NodeFlags.Ambient && !isValidTypeOnlyAliasUseSite(node) && (!redirect || !shouldPreserveConstEnums(redirect.commandLine.options))) { error(node, Diagnostics.Cannot_access_ambient_const_enums_when_0_is_enabled, isolatedModulesLikeFlagName); } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 0bce5b25a9a06..c46e910a53d2b 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1925,6 +1925,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg getResolvedProjectReferenceByPath, forEachResolvedProjectReference, isSourceOfProjectReferenceRedirect, + getRedirectReferenceForResolutionFromSourceOfProject, emitBuildInfo, fileExists, readFile, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e67537f0bb326..9e06679f0185f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4734,9 +4734,14 @@ export interface Program extends ScriptReferenceHost { getProjectReferences(): readonly ProjectReference[] | undefined; getResolvedProjectReferences(): readonly (ResolvedProjectReference | undefined)[] | undefined; /** @internal */ getProjectReferenceRedirect(fileName: string): string | undefined; - /** @internal */ getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined; + /** + * @internal + * Get the referenced project if the file is input file from that reference project + */ + getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined; /** @internal */ forEachResolvedProjectReference(cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined): T | undefined; /** @internal */ getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined; + /** @internal */ getRedirectReferenceForResolutionFromSourceOfProject(filePath: Path): ResolvedProjectReference | undefined; /** @internal */ isSourceOfProjectReferenceRedirect(fileName: string): boolean; /** @internal */ getBuildInfo?(): BuildInfo; /** @internal */ emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult; @@ -4853,6 +4858,7 @@ export interface TypeCheckerHost extends ModuleSpecifierResolutionHost { getSourceFile(fileName: string): SourceFile | undefined; getProjectReferenceRedirect(fileName: string): string | undefined; isSourceOfProjectReferenceRedirect(fileName: string): boolean; + getRedirectReferenceForResolutionFromSourceOfProject(filePath: Path): ResolvedProjectReference | undefined; getModeForUsageLocation(file: SourceFile, usage: StringLiteralLike): ResolutionMode; getResolvedModule(f: SourceFile, moduleName: string, mode: ResolutionMode): ResolvedModuleWithFailedLookupLocations | undefined; diff --git a/src/testRunner/unittests/tsc/projectReferences.ts b/src/testRunner/unittests/tsc/projectReferences.ts index 8a0a9394e7316..18049687745cf 100644 --- a/src/testRunner/unittests/tsc/projectReferences.ts +++ b/src/testRunner/unittests/tsc/projectReferences.ts @@ -49,4 +49,31 @@ describe("unittests:: tsc:: projectReferences::", () => { }), commandLineArgs: ["--p", "src/project"], }); + + verifyTsc({ + scenario: "projectReferences", + subScenario: "referencing ambient const enum from referenced project with preserveConstEnums", + fs: () => + loadProjectFromFiles({ + "/src/utils/index.ts": "export const enum E { A = 1 }", + "/src/utils/index.d.ts": "export declare const enum E { A = 1 }", + "/src/utils/tsconfig.json": jsonToReadableText({ + compilerOptions: { + composite: true, + declaration: true, + preserveConstEnums: true, + }, + }), + "/src/project/index.ts": `import { E } from "../utils"; E.A;`, + "/src/project/tsconfig.json": jsonToReadableText({ + compilerOptions: { + isolatedModules: true, + }, + references: [ + { path: "../utils" }, + ], + }), + }), + commandLineArgs: ["--p", "src/project"], + }); }); diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index ffe0db407e4a6..7ed29fa1f3af7 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -347,6 +347,35 @@ function foo() { baselineTsserverLogs("projectReferences", "reusing d.ts files from composite and non composite projects", session); }); + it("referencing const enum from referenced project with preserveConstEnums", () => { + const projectLocation = `/user/username/projects/project`; + const utilsIndex: File = { + path: `${projectLocation}/src/utils/index.ts`, + content: "export const enum E { A = 1 }", + }; + const utilsDeclaration: File = { + path: `${projectLocation}/src/utils/index.d.ts`, + content: "export declare const enum E { A = 1 }", + }; + const utilsConfig: File = { + path: `${projectLocation}/src/utils/tsconfig.json`, + content: jsonToReadableText({ compilerOptions: { composite: true, declaration: true, preserveConstEnums: true } }), + }; + const projectIndex: File = { + path: `${projectLocation}/src/project/index.ts`, + content: `import { E } from "../utils"; E.A;`, + }; + const projectConfig: File = { + path: `${projectLocation}/src/project/tsconfig.json`, + content: jsonToReadableText({ compilerOptions: { isolatedModules: true }, references: [{ path: "../utils" }] }), + }; + const host = createServerHost([libFile, utilsIndex, utilsDeclaration, utilsConfig, projectIndex, projectConfig]); + const session = new TestSession(host); + openFilesForSession([projectIndex], session); + verifyGetErrRequest({ session, files: [projectIndex] }); + baselineTsserverLogs("projectReferences", `referencing const enum from referenced project with preserveConstEnums`, session); + }); + describe("when references are monorepo like with symlinks", () => { interface Packages { bPackageJson: File; diff --git a/tests/baselines/reference/tsc/projectReferences/referencing-ambient-const-enum-from-referenced-project-with-preserveConstEnums.js b/tests/baselines/reference/tsc/projectReferences/referencing-ambient-const-enum-from-referenced-project-with-preserveConstEnums.js new file mode 100644 index 0000000000000..249b06cfa1c65 --- /dev/null +++ b/tests/baselines/reference/tsc/projectReferences/referencing-ambient-const-enum-from-referenced-project-with-preserveConstEnums.js @@ -0,0 +1,61 @@ +currentDirectory:: / useCaseSensitiveFileNames: false +Input:: +//// [/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +declare const console: { log(msg: any): void; }; + +//// [/src/project/index.ts] +import { E } from "../utils"; E.A; + +//// [/src/project/tsconfig.json] +{ + "compilerOptions": { + "isolatedModules": true + }, + "references": [ + { + "path": "../utils" + } + ] +} + +//// [/src/utils/index.d.ts] +export declare const enum E { A = 1 } + +//// [/src/utils/index.ts] +export const enum E { A = 1 } + +//// [/src/utils/tsconfig.json] +{ + "compilerOptions": { + "composite": true, + "declaration": true, + "preserveConstEnums": true + } +} + + + +Output:: +/lib/tsc --p src/project +exitCode:: ExitStatus.Success + + +//// [/src/project/index.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var utils_1 = require("../utils"); +utils_1.E.A; + + diff --git a/tests/baselines/reference/tsserver/projectReferences/referencing-const-enum-from-referenced-project-with-preserveConstEnums.js b/tests/baselines/reference/tsserver/projectReferences/referencing-const-enum-from-referenced-project-with-preserveConstEnums.js new file mode 100644 index 0000000000000..e2a476274a9e0 --- /dev/null +++ b/tests/baselines/reference/tsserver/projectReferences/referencing-const-enum-from-referenced-project-with-preserveConstEnums.js @@ -0,0 +1,337 @@ +currentDirectory:: / useCaseSensitiveFileNames: false +Info seq [hh:mm:ss:mss] Provided types map file "/typesMap.json" doesn't exist +Before request +//// [/a/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } + +//// [/user/username/projects/project/src/utils/index.ts] +export const enum E { A = 1 } + +//// [/user/username/projects/project/src/utils/index.d.ts] +export declare const enum E { A = 1 } + +//// [/user/username/projects/project/src/utils/tsconfig.json] +{ + "compilerOptions": { + "composite": true, + "declaration": true, + "preserveConstEnums": true + } +} + +//// [/user/username/projects/project/src/project/index.ts] +import { E } from "../utils"; E.A; + +//// [/user/username/projects/project/src/project/tsconfig.json] +{ + "compilerOptions": { + "isolatedModules": true + }, + "references": [ + { + "path": "../utils" + } + ] +} + + +Info seq [hh:mm:ss:mss] request: + { + "command": "open", + "arguments": { + "file": "/user/username/projects/project/src/project/index.ts" + }, + "seq": 1, + "type": "request" + } +Info seq [hh:mm:ss:mss] Search path: /user/username/projects/project/src/project +Info seq [hh:mm:ss:mss] For info: /user/username/projects/project/src/project/index.ts :: Config file name: /user/username/projects/project/src/project/tsconfig.json +Info seq [hh:mm:ss:mss] Creating configuration project /user/username/projects/project/src/project/tsconfig.json +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /user/username/projects/project/src/project/tsconfig.json 2000 undefined Project: /user/username/projects/project/src/project/tsconfig.json WatchType: Config file +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "projectLoadingStart", + "body": { + "projectName": "/user/username/projects/project/src/project/tsconfig.json", + "reason": "Creating possible configured project for /user/username/projects/project/src/project/index.ts to open" + } + } +Info seq [hh:mm:ss:mss] Config: /user/username/projects/project/src/project/tsconfig.json : { + "rootNames": [ + "/user/username/projects/project/src/project/index.ts" + ], + "options": { + "isolatedModules": true, + "configFilePath": "/user/username/projects/project/src/project/tsconfig.json" + }, + "projectReferences": [ + { + "path": "/user/username/projects/project/src/utils", + "originalPath": "../utils" + } + ] +} +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project/src/project 1 undefined Config: /user/username/projects/project/src/project/tsconfig.json WatchType: Wild card directory +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project/src/project 1 undefined Config: /user/username/projects/project/src/project/tsconfig.json WatchType: Wild card directory +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /user/username/projects/project/src/project/tsconfig.json +Info seq [hh:mm:ss:mss] Config: /user/username/projects/project/src/utils/tsconfig.json : { + "rootNames": [ + "/user/username/projects/project/src/utils/index.ts" + ], + "options": { + "composite": true, + "declaration": true, + "preserveConstEnums": true, + "configFilePath": "/user/username/projects/project/src/utils/tsconfig.json" + } +} +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /user/username/projects/project/src/utils/tsconfig.json 2000 undefined Project: /user/username/projects/project/src/project/tsconfig.json WatchType: Config file +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project/src/utils 1 undefined Config: /user/username/projects/project/src/utils/tsconfig.json WatchType: Wild card directory +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project/src/utils 1 undefined Config: /user/username/projects/project/src/utils/tsconfig.json WatchType: Wild card directory +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project/src 0 undefined Project: /user/username/projects/project/src/project/tsconfig.json WatchType: Failed Lookup Locations +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project/src 0 undefined Project: /user/username/projects/project/src/project/tsconfig.json WatchType: Failed Lookup Locations +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project/src/utils 1 undefined Project: /user/username/projects/project/src/project/tsconfig.json WatchType: Failed Lookup Locations +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project/src/utils 1 undefined Project: /user/username/projects/project/src/project/tsconfig.json WatchType: Failed Lookup Locations +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /user/username/projects/project/src/utils/index.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project/src/project/node_modules/@types 1 undefined Project: /user/username/projects/project/src/project/tsconfig.json WatchType: Type roots +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project/src/project/node_modules/@types 1 undefined Project: /user/username/projects/project/src/project/tsconfig.json WatchType: Type roots +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project/src/node_modules/@types 1 undefined Project: /user/username/projects/project/src/project/tsconfig.json WatchType: Type roots +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project/src/node_modules/@types 1 undefined Project: /user/username/projects/project/src/project/tsconfig.json WatchType: Type roots +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project/node_modules/@types 1 undefined Project: /user/username/projects/project/src/project/tsconfig.json WatchType: Type roots +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/project/node_modules/@types 1 undefined Project: /user/username/projects/project/src/project/tsconfig.json WatchType: Type roots +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/node_modules/@types 1 undefined Project: /user/username/projects/project/src/project/tsconfig.json WatchType: Type roots +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/node_modules/@types 1 undefined Project: /user/username/projects/project/src/project/tsconfig.json WatchType: Type roots +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /user/username/projects/project/src/project/tsconfig.json projectStateVersion: 1 projectProgramVersion: 0 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/user/username/projects/project/src/project/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (3) + /a/lib/lib.d.ts Text-1 "/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }" + /user/username/projects/project/src/utils/index.ts Text-1 "export const enum E { A = 1 }" + /user/username/projects/project/src/project/index.ts SVC-1-0 "import { E } from \"../utils\"; E.A;" + + + ../../../../../../a/lib/lib.d.ts + Default library for target 'es5' + ../utils/index.ts + Imported via "../utils" from file 'index.ts' + index.ts + Matched by default include pattern '**/*' + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "projectLoadingFinish", + "body": { + "projectName": "/user/username/projects/project/src/project/tsconfig.json" + } + } +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "telemetry", + "body": { + "telemetryEventName": "projectInfo", + "payload": { + "projectId": "0ecf3cccdd8e71701ab41fc1852f59ac0c6c0d069aea4eb2fba6867e4bd6f54c", + "fileStats": { + "js": 0, + "jsSize": 0, + "jsx": 0, + "jsxSize": 0, + "ts": 2, + "tsSize": 63, + "tsx": 0, + "tsxSize": 0, + "dts": 1, + "dtsSize": 334, + "deferred": 0, + "deferredSize": 0 + }, + "compilerOptions": { + "isolatedModules": true + }, + "typeAcquisition": { + "enable": false, + "include": false, + "exclude": false + }, + "extends": false, + "files": false, + "include": false, + "exclude": false, + "compileOnSave": false, + "configFileName": "tsconfig.json", + "projectType": "configured", + "languageServiceEnabled": true, + "version": "FakeVersion" + } + } + } +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "configFileDiag", + "body": { + "triggerFile": "/user/username/projects/project/src/project/index.ts", + "configFile": "/user/username/projects/project/src/project/tsconfig.json", + "diagnostics": [] + } + } +Info seq [hh:mm:ss:mss] Project '/user/username/projects/project/src/project/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (3) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Open files: +Info seq [hh:mm:ss:mss] FileName: /user/username/projects/project/src/project/index.ts ProjectRootPath: undefined +Info seq [hh:mm:ss:mss] Projects: /user/username/projects/project/src/project/tsconfig.json +Info seq [hh:mm:ss:mss] response: + { + "responseRequired": false + } +After request + +PolledWatches:: +/user/username/projects/node_modules/@types: *new* + {"pollingInterval":500} +/user/username/projects/project/node_modules/@types: *new* + {"pollingInterval":500} +/user/username/projects/project/src/node_modules/@types: *new* + {"pollingInterval":500} +/user/username/projects/project/src/project/node_modules/@types: *new* + {"pollingInterval":500} + +FsWatches:: +/a/lib/lib.d.ts: *new* + {} +/user/username/projects/project/src: *new* + {} +/user/username/projects/project/src/project/tsconfig.json: *new* + {} +/user/username/projects/project/src/utils/index.ts: *new* + {} +/user/username/projects/project/src/utils/tsconfig.json: *new* + {} + +FsWatchesRecursive:: +/user/username/projects/project/src/project: *new* + {} +/user/username/projects/project/src/utils: *new* + {} + +Projects:: +/user/username/projects/project/src/project/tsconfig.json (Configured) *new* + projectStateVersion: 1 + projectProgramVersion: 1 + +ScriptInfos:: +/a/lib/lib.d.ts *new* + version: Text-1 + containingProjects: 1 + /user/username/projects/project/src/project/tsconfig.json +/user/username/projects/project/src/project/index.ts (Open) *new* + version: SVC-1-0 + containingProjects: 1 + /user/username/projects/project/src/project/tsconfig.json *default* +/user/username/projects/project/src/utils/index.ts *new* + version: Text-1 + containingProjects: 1 + /user/username/projects/project/src/project/tsconfig.json + +Before request + +Info seq [hh:mm:ss:mss] request: + { + "command": "geterr", + "arguments": { + "delay": 0, + "files": [ + "/user/username/projects/project/src/project/index.ts" + ] + }, + "seq": 2, + "type": "request" + } +Info seq [hh:mm:ss:mss] response: + { + "responseRequired": false + } +After request + +Timeout callback:: count: 1 +1: checkOne *new* + +Before running Timeout callback:: count: 1 +1: checkOne + +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "syntaxDiag", + "body": { + "file": "/user/username/projects/project/src/project/index.ts", + "diagnostics": [] + } + } +After running Timeout callback:: count: 0 + +Immedidate callback:: count: 1 +1: semanticCheck *new* + +Before running Immedidate callback:: count: 1 +1: semanticCheck + +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "semanticDiag", + "body": { + "file": "/user/username/projects/project/src/project/index.ts", + "diagnostics": [] + } + } +After running Immedidate callback:: count: 1 + +Immedidate callback:: count: 1 +2: suggestionCheck *new* + +Before running Immedidate callback:: count: 1 +2: suggestionCheck + +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "suggestionDiag", + "body": { + "file": "/user/username/projects/project/src/project/index.ts", + "diagnostics": [] + } + } +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "requestCompleted", + "body": { + "request_seq": 2 + } + } +After running Immedidate callback:: count: 0