diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b962dfd0c6d74..3ae4f5f743585 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4234,7 +4234,7 @@ namespace ts { getCommonSourceDirectory: !!(host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "", getSourceFiles: () => host.getSourceFiles(), getCurrentDirectory: () => host.getCurrentDirectory(), - getProbableSymlinks: maybeBind(host, host.getProbableSymlinks), + getSymlinkCache: maybeBind(host, host.getSymlinkCache), useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames), redirectTargetsMap: host.redirectTargetsMap, getProjectReferenceRedirect: fileName => host.getProjectReferenceRedirect(fileName), diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 96925b838e4ed..54be63be022fa 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -185,20 +185,22 @@ namespace ts.moduleSpecifiers { const result = forEach(targets, cb); if (result) return result; } - const links = host.getProbableSymlinks - ? host.getProbableSymlinks(host.getSourceFiles()) + const links = host.getSymlinkCache + ? host.getSymlinkCache() : discoverProbableSymlinks(host.getSourceFiles(), getCanonicalFileName, cwd); + const symlinkedDirectories = links.getSymlinkedDirectories(); const compareStrings = (!host.useCaseSensitiveFileNames || host.useCaseSensitiveFileNames()) ? compareStringsCaseSensitive : compareStringsCaseInsensitive; - const result = forEachEntry(links, (resolved, path) => { - if (startsWithDirectory(importingFileName, resolved, getCanonicalFileName)) { + const result = symlinkedDirectories && forEachEntry(symlinkedDirectories, (resolved, path) => { + if (resolved === false) return undefined; + if (startsWithDirectory(importingFileName, resolved.realPath, getCanonicalFileName)) { return undefined; // Don't want to a package to globally import from itself } - const target = find(targets, t => compareStrings(t.slice(0, resolved.length + 1), resolved + "/") === Comparison.EqualTo); + const target = find(targets, t => compareStrings(t.slice(0, resolved.real.length), resolved.real) === Comparison.EqualTo); if (target === undefined) return undefined; - const relative = getRelativePathFromDirectory(resolved, target, getCanonicalFileName); + const relative = getRelativePathFromDirectory(resolved.real, target, getCanonicalFileName); const option = resolvePath(path, relative); if (!host.fileExists || host.fileExists(option)) { const result = cb(option); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 132938d5fb8c7..87f4637258cb0 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -702,7 +702,7 @@ namespace ts { let processingDefaultLibFiles: SourceFile[] | undefined; let processingOtherFiles: SourceFile[] | undefined; let files: SourceFile[]; - let symlinks: ReadonlyESMap | undefined; + let symlinks: SymlinkCache | undefined; let commonSourceDirectory: string; let diagnosticsProducingTypeChecker: TypeChecker; let noDiagnosticsTypeChecker: TypeChecker; @@ -811,8 +811,9 @@ namespace ts { const useSourceOfProjectReferenceRedirect = !!host.useSourceOfProjectReferenceRedirect?.() && !options.disableSourceOfProjectReferenceRedirect; - const { onProgramCreateComplete, fileExists } = updateHostForUseSourceOfProjectReferenceRedirect({ + const { onProgramCreateComplete, fileExists, directoryExists } = updateHostForUseSourceOfProjectReferenceRedirect({ compilerHost: host, + getSymlinkCache, useSourceOfProjectReferenceRedirect, toPath, getResolvedProjectReferences, @@ -974,7 +975,9 @@ namespace ts { isSourceOfProjectReferenceRedirect, emitBuildInfo, fileExists, - getProbableSymlinks, + directoryExists, + getSymlinkCache, + realpath: host.realpath?.bind(host), useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), }; @@ -1490,7 +1493,7 @@ namespace ts { getResolvedProjectReferenceToRedirect, getProjectReferenceRedirect, isSourceOfProjectReferenceRedirect, - getProbableSymlinks, + getSymlinkCache, writeFile: writeFileCallback || ( (fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)), isEmitBlocked, @@ -3468,9 +3471,9 @@ namespace ts { return comparePaths(file1, file2, currentDirectory, !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; } - function getProbableSymlinks(): ReadonlyESMap { - if (host.getSymlinks) { - return host.getSymlinks(); + function getSymlinkCache(): SymlinkCache { + if (host.getSymlinkCache) { + return host.getSymlinkCache(); } return symlinks || (symlinks = discoverProbableSymlinks( files, @@ -3479,13 +3482,9 @@ namespace ts { } } - interface SymlinkedDirectory { - real: string; - realPath: Path; - } - interface HostForUseSourceOfProjectReferenceRedirect { compilerHost: CompilerHost; + getSymlinkCache: () => SymlinkCache; useSourceOfProjectReferenceRedirect: boolean; toPath(fileName: string): Path; getResolvedProjectReferences(): readonly (ResolvedProjectReference | undefined)[] | undefined; @@ -3495,9 +3494,6 @@ namespace ts { function updateHostForUseSourceOfProjectReferenceRedirect(host: HostForUseSourceOfProjectReferenceRedirect) { let setOfDeclarationDirectories: Set | undefined; - let symlinkedDirectories: ESMap | undefined; - let symlinkedFiles: ESMap | undefined; - const originalFileExists = host.compilerHost.fileExists; const originalDirectoryExists = host.compilerHost.directoryExists; const originalGetDirectories = host.compilerHost.getDirectories; @@ -3507,11 +3503,12 @@ namespace ts { host.compilerHost.fileExists = fileExists; + let directoryExists; if (originalDirectoryExists) { // This implementation of directoryExists checks if the directory being requested is // directory of .d.ts file for the referenced Project. // If it is it returns true irrespective of whether that directory exists on host - host.compilerHost.directoryExists = path => { + directoryExists = host.compilerHost.directoryExists = path => { if (originalDirectoryExists.call(host.compilerHost, path)) { handleDirectoryCouldBeSymlink(path); return true; @@ -3553,11 +3550,11 @@ namespace ts { // This is something we keep for life time of the host if (originalRealpath) { host.compilerHost.realpath = s => - symlinkedFiles?.get(host.toPath(s)) || + host.getSymlinkCache().getSymlinkedFiles()?.get(host.toPath(s)) || originalRealpath.call(host.compilerHost, s); } - return { onProgramCreateComplete, fileExists }; + return { onProgramCreateComplete, fileExists, directoryExists }; function onProgramCreateComplete() { host.compilerHost.fileExists = originalFileExists; @@ -3603,20 +3600,20 @@ namespace ts { // Because we already watch node_modules, handle symlinks in there if (!originalRealpath || !stringContains(directory, nodeModulesPathPart)) return; - if (!symlinkedDirectories) symlinkedDirectories = new Map(); + const symlinkCache = host.getSymlinkCache(); const directoryPath = ensureTrailingDirectorySeparator(host.toPath(directory)); - if (symlinkedDirectories.has(directoryPath)) return; + if (symlinkCache.getSymlinkedDirectories()?.has(directoryPath)) return; const real = normalizePath(originalRealpath.call(host.compilerHost, directory)); let realPath: Path; if (real === directory || (realPath = ensureTrailingDirectorySeparator(host.toPath(real))) === directoryPath) { // not symlinked - symlinkedDirectories.set(directoryPath, false); + symlinkCache.setSymlinkedDirectory(directoryPath, false); return; } - symlinkedDirectories.set(directoryPath, { + symlinkCache.setSymlinkedDirectory(directoryPath, { real: ensureTrailingDirectorySeparator(real), realPath }); @@ -3630,10 +3627,12 @@ namespace ts { const result = fileOrDirectoryExistsUsingSource(fileOrDirectory); if (result !== undefined) return result; + const symlinkCache = host.getSymlinkCache(); + const symlinkedDirectories = symlinkCache.getSymlinkedDirectories(); if (!symlinkedDirectories) return false; const fileOrDirectoryPath = host.toPath(fileOrDirectory); if (!stringContains(fileOrDirectoryPath, nodeModulesPathPart)) return false; - if (isFile && symlinkedFiles && symlinkedFiles.has(fileOrDirectoryPath)) return true; + if (isFile && symlinkCache.getSymlinkedFiles()?.has(fileOrDirectoryPath)) return true; // If it contains node_modules check if its one of the symlinked path we know of return firstDefinedIterator( @@ -3642,10 +3641,9 @@ namespace ts { if (!symlinkedDirectory || !startsWith(fileOrDirectoryPath, directoryPath)) return undefined; const result = fileOrDirectoryExistsUsingSource(fileOrDirectoryPath.replace(directoryPath, symlinkedDirectory.realPath)); if (isFile && result) { - if (!symlinkedFiles) symlinkedFiles = new Map(); // Store the real path for the file' const absolutePath = getNormalizedAbsolutePath(fileOrDirectory, host.compilerHost.getCurrentDirectory()); - symlinkedFiles.set( + symlinkCache.setSymlinkedFile( fileOrDirectoryPath, `${symlinkedDirectory.real}${absolutePath.replace(new RegExp(directoryPath, "i"), "")}` ); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 67065ca804647..43936cbce672a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3767,7 +3767,6 @@ namespace ts { /*@internal*/ isSourceOfProjectReferenceRedirect(fileName: string): boolean; /*@internal*/ getProgramBuildInfo?(): ProgramBuildInfo | undefined; /*@internal*/ emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult; - /*@internal*/ getProbableSymlinks(): ReadonlyESMap; /** * This implementation handles file exists to be true if file is source of project reference redirect when program is created using useSourceOfProjectReferenceRedirect */ @@ -6240,7 +6239,7 @@ namespace ts { // TODO: later handle this in better way in builder host instead once the api for tsbuild finalizes and doesn't use compilerHost as base /*@internal*/createDirectory?(directory: string): void; - /*@internal*/getSymlinks?(): ReadonlyESMap; + /*@internal*/getSymlinkCache?(): SymlinkCache; } /** true if --out otherwise source file name */ @@ -7754,8 +7753,10 @@ namespace ts { useCaseSensitiveFileNames?(): boolean; fileExists(path: string): boolean; getCurrentDirectory(): string; + directoryExists?(path: string): boolean; readFile?(path: string): string | undefined; - getProbableSymlinks?(files: readonly SourceFile[]): ReadonlyESMap; + realpath?(path: string): string; + getSymlinkCache?(): SymlinkCache; getGlobalTypingsCacheLocation?(): string | undefined; getSourceFiles(): readonly SourceFile[]; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 724e6a8754c01..41d513a252303 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5926,28 +5926,57 @@ namespace ts { return true; } - export function discoverProbableSymlinks(files: readonly SourceFile[], getCanonicalFileName: GetCanonicalFileName, cwd: string): ReadonlyESMap { - const result = new Map(); + export interface SymlinkedDirectory { + real: string; + realPath: Path; + } + + export interface SymlinkCache { + getSymlinkedDirectories(): ReadonlyESMap | undefined; + getSymlinkedFiles(): ReadonlyESMap | undefined; + setSymlinkedDirectory(path: Path, directory: SymlinkedDirectory | false): void; + setSymlinkedFile(path: Path, real: string): void; + } + + export function createSymlinkCache(): SymlinkCache { + let symlinkedDirectories: ESMap | undefined; + let symlinkedFiles: ESMap | undefined; + return { + getSymlinkedFiles: () => symlinkedFiles, + getSymlinkedDirectories: () => symlinkedDirectories, + setSymlinkedFile: (path, real) => (symlinkedFiles || (symlinkedFiles = new Map())).set(path, real), + setSymlinkedDirectory: (path, directory) => (symlinkedDirectories || (symlinkedDirectories = new Map())).set(path, directory), + }; + } + + export function discoverProbableSymlinks(files: readonly SourceFile[], getCanonicalFileName: GetCanonicalFileName, cwd: string): SymlinkCache { + const cache = createSymlinkCache(); const symlinks = flatten(mapDefined(files, sf => sf.resolvedModules && compact(arrayFrom(mapIterator(sf.resolvedModules.values(), res => res && res.originalPath && res.resolvedFileName !== res.originalPath ? [res.resolvedFileName, res.originalPath] as const : undefined))))); for (const [resolvedPath, originalPath] of symlinks) { - const [commonResolved, commonOriginal] = guessDirectorySymlink(resolvedPath, originalPath, cwd, getCanonicalFileName); - result.set(commonOriginal, commonResolved); + const [commonResolved, commonOriginal] = guessDirectorySymlink(resolvedPath, originalPath, cwd, getCanonicalFileName) || emptyArray; + if (commonResolved && commonOriginal) { + cache.setSymlinkedDirectory( + toPath(commonOriginal, cwd, getCanonicalFileName), + { real: commonResolved, realPath: toPath(commonResolved, cwd, getCanonicalFileName) }); + } } - return result; + return cache; } - function guessDirectorySymlink(a: string, b: string, cwd: string, getCanonicalFileName: GetCanonicalFileName): [string, string] { + function guessDirectorySymlink(a: string, b: string, cwd: string, getCanonicalFileName: GetCanonicalFileName): [string, string] | undefined { const aParts = getPathComponents(toPath(a, cwd, getCanonicalFileName)); const bParts = getPathComponents(toPath(b, cwd, getCanonicalFileName)); + let isDirectory = false; while (!isNodeModulesOrScopedPackageDirectory(aParts[aParts.length - 2], getCanonicalFileName) && !isNodeModulesOrScopedPackageDirectory(bParts[bParts.length - 2], getCanonicalFileName) && getCanonicalFileName(aParts[aParts.length - 1]) === getCanonicalFileName(bParts[bParts.length - 1])) { aParts.pop(); bParts.pop(); + isDirectory = true; } - return [getPathFromPathComponents(aParts), getPathFromPathComponents(bParts)]; + return isDirectory ? [getPathFromPathComponents(aParts), getPathFromPathComponents(bParts)] : undefined; } // KLUDGE: Don't assume one 'node_modules' links to another. More likely a single directory inside the node_modules is the symlink. diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 6c3ee40eee068..69d7238cbec15 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -154,6 +154,19 @@ namespace Harness.LanguageService { return fileNames; } + public realpath(path: string): string { + try { + return this.vfs.realpathSync(path); + } + catch { + return path; + } + } + + public directoryExists(path: string) { + return this.vfs.statSync(path).isDirectory(); + } + public getScriptInfo(fileName: string): ScriptInfo | undefined { return this.scriptInfos.get(vpath.resolve(this.vfs.cwd(), fileName)); } @@ -720,10 +733,15 @@ namespace Harness.LanguageService { fileName = Compiler.defaultLibFileName; } - const snapshot = this.host.getScriptSnapshot(fileName); + // System FS would follow symlinks, even though snapshots are stored by original file name + const snapshot = this.host.getScriptSnapshot(fileName) || this.host.getScriptSnapshot(this.realpath(fileName)); return snapshot && ts.getSnapshotText(snapshot); } + realpath(path: string) { + return this.host.realpath(path); + } + writeFile = ts.noop; resolvePath(path: string): string { @@ -731,7 +749,7 @@ namespace Harness.LanguageService { } fileExists(path: string): boolean { - return !!this.host.getScriptSnapshot(path); + return this.host.fileExists(path); } directoryExists(): boolean { diff --git a/src/server/project.ts b/src/server/project.ts index 413616f5667fa..a19dbb304b6fc 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -246,7 +246,7 @@ namespace ts.server { /*@internal*/ private dirtyFilesForSuggestions: Set | undefined; /*@internal*/ - private symlinks: ReadonlyESMap | undefined; + private symlinks: SymlinkCache | undefined; /*@internal*/ autoImportProviderHost: AutoImportProviderProject | false | undefined; @@ -328,9 +328,9 @@ namespace ts.server { } /*@internal*/ - getProbableSymlinks(files: readonly SourceFile[]): ReadonlyESMap { + getSymlinkCache(): SymlinkCache { return this.symlinks || (this.symlinks = discoverProbableSymlinks( - files, + this.program?.getSourceFiles() || emptyArray, this.getCanonicalFileName, this.getCurrentDirectory())); } @@ -1640,6 +1640,22 @@ namespace ts.server { return this.projectService.includePackageJsonAutoImports(); } + /*@internal*/ + getModuleResolutionHostForAutoImportProvider(): ModuleResolutionHost { + if (this.program) { + return { + fileExists: this.program.fileExists, + directoryExists: this.program.directoryExists, + realpath: this.program.realpath || this.projectService.host.realpath?.bind(this.projectService.host), + getCurrentDirectory: this.getCurrentDirectory.bind(this), + readFile: this.projectService.host.readFile.bind(this.projectService.host), + getDirectories: this.projectService.host.getDirectories.bind(this.projectService.host), + trace: this.projectService.host.trace?.bind(this.projectService.host), + }; + } + return this.projectService.host; + } + /*@internal*/ getPackageJsonAutoImportProvider(): Program | undefined { if (this.autoImportProviderHost === false) { @@ -1657,7 +1673,7 @@ namespace ts.server { const dependencySelection = this.includePackageJsonAutoImports(); if (dependencySelection) { - this.autoImportProviderHost = AutoImportProviderProject.create(dependencySelection, this, this.projectService.host, this.documentRegistry); + this.autoImportProviderHost = AutoImportProviderProject.create(dependencySelection, this, this.getModuleResolutionHostForAutoImportProvider(), this.documentRegistry); if (this.autoImportProviderHost) { updateProjectIfDirty(this.autoImportProviderHost); return this.autoImportProviderHost.getCurrentProgram(); @@ -1841,8 +1857,11 @@ namespace ts.server { moduleResolutionHost)); for (const resolution of resolutions) { - if (resolution.resolvedTypeReferenceDirective?.resolvedFileName && !hostProject.getCurrentProgram()!.getSourceFile(resolution.resolvedTypeReferenceDirective.resolvedFileName)) { - rootNames = append(rootNames, resolution.resolvedTypeReferenceDirective.resolvedFileName); + if (!resolution.resolvedTypeReferenceDirective?.resolvedFileName) continue; + const { resolvedFileName } = resolution.resolvedTypeReferenceDirective; + const fileName = moduleResolutionHost.realpath?.(resolvedFileName) || resolvedFileName; + if (!hostProject.getCurrentProgram()!.getSourceFile(fileName) && !hostProject.getCurrentProgram()!.getSourceFile(resolvedFileName)) { + rootNames = append(rootNames, fileName); } } } @@ -1869,7 +1888,7 @@ namespace ts.server { skipLibCheck: true, types: ts.emptyArray, lib: ts.emptyArray, - sourceMap: false + sourceMap: false, }; const rootNames = this.getRootFileNames(dependencySelection, hostProject, moduleResolutionHost, compilerOptions); @@ -1914,7 +1933,7 @@ namespace ts.server { rootFileNames = AutoImportProviderProject.getRootFileNames( this.hostProject.includePackageJsonAutoImports(), this.hostProject, - this.projectService.host, + this.hostProject.getModuleResolutionHostForAutoImportProvider(), this.getCompilationSettings()); } @@ -1941,6 +1960,18 @@ namespace ts.server { throw new Error("AutoImportProviderProject is an auto import provider; use `markAsDirty()` instead."); } + getModuleResolutionHostForAutoImportProvider(): never { + throw new Error("AutoImportProviderProject cannot provide its own host; use `hostProject.getModuleResolutionHostForAutomImportProvider()` instead."); + } + + getProjectReferences() { + return this.hostProject.getProjectReferences(); + } + + useSourceOfProjectReferenceRedirect() { + return true; + } + /*@internal*/ includePackageJsonAutoImports() { return PackageJsonAutoImportPreference.None; @@ -1949,6 +1980,11 @@ namespace ts.server { getTypeAcquisition(): TypeAcquisition { return { enable: false }; } + + /*@internal*/ + getSymlinkCache() { + return this.hostProject.getSymlinkCache(); + } } /** diff --git a/src/services/services.ts b/src/services/services.ts index 04d6450d548ac..a2cf8f381c200 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1296,6 +1296,7 @@ namespace ts { getCurrentDirectory: () => currentDirectory, fileExists, readFile, + getSymlinkCache: maybeBind(host, host.getSymlinkCache), realpath: maybeBind(host, host.realpath), directoryExists: directoryName => { return directoryProbablyExists(directoryName, host); diff --git a/src/services/types.ts b/src/services/types.ts index 9eee73f99d4d8..0ebd80372cbea 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -272,7 +272,7 @@ namespace ts { /* @internal */ getGlobalTypingsCacheLocation?(): string | undefined; /* @internal */ - getProbableSymlinks?(files: readonly SourceFile[]): ReadonlyESMap; + getSymlinkCache?(files?: readonly SourceFile[]): SymlinkCache; /* * Required for full import and type reference completions. diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 4c841278f42d4..b3a09b1e5559b 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1760,7 +1760,7 @@ namespace ts { getCurrentDirectory: () => host.getCurrentDirectory(), readFile: maybeBind(host, host.readFile), useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames), - getProbableSymlinks: maybeBind(host, host.getProbableSymlinks) || (() => program.getProbableSymlinks()), + getSymlinkCache: maybeBind(host, host.getSymlinkCache) || program.getSymlinkCache, getGlobalTypingsCacheLocation: maybeBind(host, host.getGlobalTypingsCacheLocation), getSourceFiles: () => program.getSourceFiles(), redirectTargetsMap: program.redirectTargetsMap, diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 8442b93e4f686..27c98ed51a5fa 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -9268,6 +9268,9 @@ declare namespace ts.server { getScriptFileNames(): string[]; getLanguageService(): never; markAutoImportProviderAsDirty(): never; + getModuleResolutionHostForAutoImportProvider(): never; + getProjectReferences(): readonly ProjectReference[] | undefined; + useSourceOfProjectReferenceRedirect(): boolean; getTypeAcquisition(): TypeAcquisition; } /** diff --git a/tests/cases/fourslash/server/autoImportProvider4.ts b/tests/cases/fourslash/server/autoImportProvider4.ts new file mode 100644 index 0000000000000..b9e42014169a0 --- /dev/null +++ b/tests/cases/fourslash/server/autoImportProvider4.ts @@ -0,0 +1,24 @@ +/// + +// @Filename: /a/package.json +//// { "dependencies": { "b": "*" } } + +// @Filename: /a/tsconfig.json +//// { "compilerOptions": { "module": "commonjs", "target": "esnext" }, "references": [{ "path": "../b" }] } + +// @Filename: /a/index.ts +//// new Shape/**/ + +// @Filename: /b/package.json +//// { "types": "out/index.d.ts" } + +// @Filename: /b/tsconfig.json +//// { "compilerOptions": { "outDir": "out", "composite": true } } + +// @Filename: /b/index.ts +//// export class Shape {} + +// @link: /b -> /a/node_modules/b + +goTo.marker(); +verify.importFixAtPosition([`import { Shape } from "b";\r\n\r\nnew Shape`]);