Skip to content

Commit 87294b4

Browse files
nonaraRon S
authored and
Ron S
committed
feat: Improved resolution strategy by using EmitHost
Switched to API method to `getOwnEmitOutputFilePath`
1 parent 1b97c80 commit 87294b4

File tree

4 files changed

+76
-77
lines changed

4 files changed

+76
-77
lines changed

src/transformer.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
// noinspection ES6UnusedImports
2-
import {} from "ts-expose-internals";
3-
import path from "path";
4-
import ts from "typescript";
5-
import { cast } from "./utils";
6-
import { TsTransformPathsConfig, TsTransformPathsContext, TypeScriptThree, VisitorContext } from "./types";
7-
import { nodeVisitor } from "./visitor";
8-
import { createHarmonyFactory } from "./utils/harmony-factory";
9-
import { Minimatch } from "minimatch";
10-
import { createParsedCommandLineForProgram } from "./utils/ts-helpers";
2+
import {} from 'ts-expose-internals';
3+
import path from 'path';
4+
import ts from 'typescript';
5+
import { cast } from './utils';
6+
import { TsTransformPathsConfig, TsTransformPathsContext, TypeScriptThree, VisitorContext } from './types';
7+
import { nodeVisitor } from './visitor';
8+
import { createHarmonyFactory } from './utils/harmony-factory';
9+
import { Minimatch } from 'minimatch';
1110

1211
/* ****************************************************************************************************************** *
1312
* Transformer
@@ -42,10 +41,10 @@ export default function transformer(
4241
transformationContext,
4342
tsInstance,
4443
pathsBasePath,
44+
emitHost: transformationContext.getEmitHost(),
4545
getCanonicalFileName: tsInstance.createGetCanonicalFileName(tsInstance.sys.useCaseSensitiveFileNames),
4646
tsThreeInstance: cast<TypeScriptThree>(tsInstance),
4747
excludeMatchers: config.exclude?.map((globPattern) => new Minimatch(globPattern, { matchBase: true })),
48-
parsedCommandLine: createParsedCommandLineForProgram(tsInstance, program),
4948
outputFileNamesCache: new Map(),
5049
// Get paths patterns appropriate for TS compiler version
5150
pathsPatterns: tryParsePatterns
@@ -54,6 +53,12 @@ export default function transformer(
5453
: tsInstance.getOwnKeys(paths)
5554
};
5655

56+
if (!tsTransformPathsContext.emitHost)
57+
throw new Error(
58+
`typescript-transform-paths >= 3.1.0 requires an EmitHost in the TransformationContext to resolve properly.`
59+
+ ` Make sure you're using either ts-patch or ttypescript.`
60+
);
61+
5762
return (sourceFile: ts.SourceFile) => {
5863
const visitorContext: VisitorContext = {
5964
...tsTransformPathsContext,

src/types.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import tsThree from "./declarations/typescript3";
2-
import ts, { CompilerOptions, GetCanonicalFileName, ParsedCommandLine, Pattern } from "typescript";
3-
import { PluginConfig } from "ts-patch";
4-
import { HarmonyFactory } from "./utils/harmony-factory";
5-
import { IMinimatch } from "minimatch";
1+
import tsThree from './declarations/typescript3';
2+
import ts, { CompilerOptions, EmitHost, GetCanonicalFileName, Pattern, SourceFile } from "typescript";
3+
import { PluginConfig } from 'ts-patch';
4+
import { HarmonyFactory } from './utils/harmony-factory';
5+
import { IMinimatch } from 'minimatch';
66

77
/* ****************************************************************************************************************** */
88
// region: TS Types
@@ -47,11 +47,11 @@ export interface TsTransformPathsContext {
4747
readonly transformationContext: ts.TransformationContext;
4848
readonly rootDirs?: string[];
4949
readonly excludeMatchers: IMinimatch[] | undefined;
50-
readonly parsedCommandLine: ParsedCommandLine;
51-
readonly outputFileNamesCache: Map<string, string>;
50+
readonly outputFileNamesCache: Map<SourceFile, string>;
5251
readonly pathsBasePath: string;
5352
readonly getCanonicalFileName: GetCanonicalFileName;
54-
readonly pathsPatterns: (string | Pattern)[]
53+
readonly pathsPatterns: (string | Pattern)[];
54+
readonly emitHost: EmitHost;
5555
}
5656

5757
export interface VisitorContext extends TsTransformPathsContext {

src/utils/resolve-module-name.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { VisitorContext } from "../types";
2-
import { isBaseDir, isURL, maybeAddRelativeLocalPrefix } from "./general-utils";
3-
import * as path from "path";
4-
import { removeFileExtension, removeSuffix, ResolvedModuleFull } from "typescript";
5-
import { getOutputFile } from "./ts-helpers";
1+
import { VisitorContext } from '../types';
2+
import { isBaseDir, isURL, maybeAddRelativeLocalPrefix } from './general-utils';
3+
import * as path from 'path';
4+
import { removeFileExtension, removeSuffix, ResolvedModuleFull, SourceFile } from 'typescript';
5+
import { getOutputDirForSourceFile } from './ts-helpers';
66

77
/* ****************************************************************************************************************** */
88
// region: Types
@@ -87,6 +87,27 @@ function getPathDetail(moduleName: string, resolvedModule: ResolvedModuleFull) {
8787
};
8888
}
8989

90+
function getResolvedSourceFile(context: VisitorContext, fileName: string): SourceFile {
91+
let res: SourceFile | undefined;
92+
const { program, tsInstance } = context;
93+
94+
/* Attempt to directly pull from Program */
95+
res = program.getSourceFile(fileName) as SourceFile;
96+
if (res) return res;
97+
98+
/* Attempt to find without extension */
99+
res = (program.getSourceFiles() as SourceFile[]).find(
100+
(s) => removeFileExtension(s.fileName) === removeFileExtension(fileName)
101+
);
102+
if (res) return res;
103+
104+
/*
105+
* Create basic synthetic SourceFile for use with compiler API - Applies if SourceFile not found in program due to
106+
* import being added by another transformer
107+
*/
108+
return tsInstance.createSourceFile(fileName, ``, tsInstance.ScriptTarget.ESNext, /* setParentNodes */ false);
109+
}
110+
90111
// endregion
91112

92113
/* ****************************************************************************************************************** */
@@ -118,6 +139,8 @@ export function resolveModuleName(context: VisitorContext, moduleName: string):
118139
};
119140
}
120141

142+
const resolvedSourceFile = getResolvedSourceFile(context, resolvedModule.resolvedFileName);
143+
121144
const {
122145
indexType,
123146
resolvedBaseNameNoExtension,
@@ -134,8 +157,8 @@ export function resolveModuleName(context: VisitorContext, moduleName: string):
134157
if (outputBaseName && extName) outputBaseName = `${outputBaseName}${extName}`;
135158

136159
/* Determine output dir */
137-
let srcFileOutputDir = path.dirname(getOutputFile(context, sourceFile.fileName));
138-
let moduleFileOutputDir = implicitPackageIndex ? resolvedDir : path.dirname(getOutputFile(context, resolvedFileName));
160+
let srcFileOutputDir = getOutputDirForSourceFile(context, sourceFile);
161+
let moduleFileOutputDir = implicitPackageIndex ? resolvedDir : getOutputDirForSourceFile(context, resolvedSourceFile);
139162

140163
// Handle rootDirs remapping
141164
if (config.useRootDirs && rootDirs) {

src/utils/ts-helpers.ts

Lines changed: 23 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,49 @@
1-
import ts, { ParsedCommandLine, Program } from "typescript";
1+
import { SourceFile } from "typescript";
22
import path from "path";
33
import { VisitorContext } from "../types";
44

55
/* ****************************************************************************************************************** */
66
// region: TS Helpers
77
/* ****************************************************************************************************************** */
88

9-
/**
10-
* Generates a ParsedCommandLine for Program
11-
*/
12-
export function createParsedCommandLineForProgram(tsInstance: typeof ts, program: Program): ParsedCommandLine {
13-
const compilerOptions = program.getCompilerOptions();
14-
const maybePcl: ParsedCommandLine | undefined = compilerOptions.configFilePath
15-
? tsInstance.getParsedCommandLineOfConfigFile(compilerOptions.configFilePath, {}, tsInstance.sys as any)
16-
: void 0;
17-
18-
return (
19-
maybePcl ??
20-
tsInstance.parseJsonConfigFileContent(
21-
{ files: program.getRootFileNames(), compilerOptions },
22-
tsInstance.sys as any,
23-
program.getCurrentDirectory()
24-
)
25-
);
26-
}
27-
289
/**
2910
* Determine output file path for source file
3011
*/
31-
export function getOutputFile(context: VisitorContext, fileName: string): string {
32-
const { tsInstance, parsedCommandLine, outputFileNamesCache, program, compilerOptions } = context;
33-
if (outputFileNamesCache.has(fileName)) return outputFileNamesCache.get(fileName)!;
12+
export function getOutputDirForSourceFile(context: VisitorContext, sourceFile: SourceFile): string {
13+
const {
14+
tsInstance,
15+
emitHost,
16+
outputFileNamesCache,
17+
compilerOptions,
18+
tsInstance: { getOwnEmitOutputFilePath, getOutputExtension },
19+
} = context;
3420

35-
let res: string | undefined = void 0;
36-
const [tsMajor, tsMinor] = tsInstance.versionMajorMinor.split(".");
21+
if (outputFileNamesCache.has(sourceFile)) return outputFileNamesCache.get(sourceFile)!;
3722

38-
// TS 3.7+ supports getOutputFileNames
39-
if (isTsProjectSourceFile(context, fileName) && (+tsMajor >= 4 || +tsMinor >= 7)) {
40-
try {
41-
res = tsInstance.getOutputFileNames(parsedCommandLine, fileName, tsInstance.sys?.useCaseSensitiveFileNames)[0];
42-
} catch (e) {
43-
console.warn(
44-
`Failed to resolve output name for ${fileName}. Please report a GH issue at: ` +
45-
`https://github.com/LeDDGroup/typescript-transform-paths/issues`
46-
);
47-
debugger;
48-
}
49-
}
23+
const outputPath = getOwnEmitOutputFilePath(sourceFile.fileName, emitHost, getOutputExtension(sourceFile, compilerOptions));
24+
if (!outputPath)
25+
throw new Error(
26+
`Could not resolve output path for ${sourceFile.fileName}. Please report a GH issue at: ` +
27+
`https://github.com/LeDDGroup/typescript-transform-paths/issues`
28+
);
5029

51-
if (!res) res = manualResolve();
30+
const res = path.dirname(outputPath);
5231

53-
outputFileNamesCache.set(fileName, res);
32+
outputFileNamesCache.set(sourceFile, res);
5433

5534
return tsInstance.normalizePath(res);
56-
57-
function manualResolve(): string {
58-
const srcDir = program.getCommonSourceDirectory();
59-
const destDir = compilerOptions.outDir ?? srcDir;
60-
return path.resolve(destDir, path.relative(srcDir, fileName));
61-
}
6235
}
6336

6437
/**
6538
* Determine if moduleName matches config in paths
6639
*/
6740
export function isModulePathsMatch(context: VisitorContext, moduleName: string): boolean {
68-
const { pathsPatterns, tsInstance: { matchPatternOrExact }} = context
41+
const {
42+
pathsPatterns,
43+
tsInstance: { matchPatternOrExact },
44+
} = context;
6945
// TODO - Remove typecast after ts v4.4
70-
return !!matchPatternOrExact((pathsPatterns as any), moduleName);
71-
}
72-
73-
export function isTsProjectSourceFile(context: VisitorContext, filePath: string): boolean {
74-
const { tsInstance, program } = context;
75-
return !!program.getRootFileNames().find((f) => tsInstance.normalizePath(filePath) === tsInstance.normalizePath(f));
46+
return !!matchPatternOrExact(pathsPatterns as any, moduleName);
7647
}
7748

7849
// endregion

0 commit comments

Comments
 (0)