Skip to content

Commit 4a16396

Browse files
committed
fix: External project references not resolved properly in composite projects (fixes #125)
1 parent 83349e4 commit 4a16396

File tree

15 files changed

+184
-30
lines changed

15 files changed

+184
-30
lines changed

src/utils/ts-helpers.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ export function getOutputDirForSourceFile(context: VisitorContext, sourceFile: S
2121

2222
if (outputFileNamesCache.has(sourceFile)) return outputFileNamesCache.get(sourceFile)!;
2323

24-
const outputPath = getOwnEmitOutputFilePath(
25-
sourceFile.fileName,
26-
emitHost,
27-
getOutputExtension(sourceFile, compilerOptions)
28-
);
24+
// Note: In project references, resolved path is different from path. In that case, our output path is already
25+
// determined in resolvedPath
26+
const outputPath =
27+
sourceFile.path && sourceFile.resolvedPath && sourceFile.path !== sourceFile.resolvedPath
28+
? sourceFile.resolvedPath
29+
: getOwnEmitOutputFilePath(sourceFile.fileName, emitHost, getOutputExtension(sourceFile, compilerOptions));
30+
2931
if (!outputPath)
3032
throw new Error(
3133
`Could not resolve output path for ${sourceFile.fileName}. Please report a GH issue at: ` +

test/tests/config.ts renamed to test/config.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ export const tsThree: typeof TypeScriptThree = require("typescript-three");
1616
/* ****************************************************************************************************************** */
1717

1818
export const tsModules = <const>[
19-
["Latest", ts, "typescript"],
2019
["3.6.5", tsThree, "typescript-three"],
20+
["Latest", ts, "typescript"],
2121
];
22-
export const projectsPaths = path.join(__dirname, "../projects");
23-
Error.stackTraceLimit = 120;
2422

25-
export const transformerPath = path.resolve(__dirname, "../../src/index.ts");
26-
export const builtTransformerPath = path.resolve(__dirname, "../../dist/index.js");
23+
export const projectsPaths = path.join(__dirname, "./projects");
24+
export const transformerPath = path.resolve(__dirname, "../src/index.ts");
25+
export const builtTransformerPath = path.resolve(__dirname, "../dist/index.js");
26+
27+
Error.stackTraceLimit = 120;
2728

2829
// endregion

test/projects/project-ref/a/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const AReffedConst = 43;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"files": [ "index.ts" ],
3+
"extends": "../tsconfig.base.json",
4+
"compilerOptions": {
5+
"outDir": "../lib/a",
6+
"declaration": true
7+
}
8+
}

test/projects/project-ref/b/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { AReffedConst } from "#a/index";
2+
export { LocalConst } from "#b/local/index";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const LocalConst = 55;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"files": [ "index.ts", "local/index.ts" ],
3+
"extends": "../tsconfig.base.json",
4+
"compilerOptions": {
5+
"outDir": "../lib/b",
6+
},
7+
"references": [
8+
{
9+
"path": "../a"
10+
}
11+
]
12+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"private": true,
3+
"name": "@tests/project-ref",
4+
"version": "0.0.0",
5+
"dependencies": {
6+
"typescript-transform-paths": "link:../../../"
7+
}
8+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"compilerOptions": {
3+
"moduleResolution": "Node",
4+
"esModuleInterop": true,
5+
"module": "ESNext",
6+
"target": "ESNext",
7+
"composite": true,
8+
"declaration": true,
9+
10+
"baseUrl": ".",
11+
"paths": {
12+
"#a/*": [ "./a/*" ],
13+
"#b/*": [ "./b/*" ]
14+
},
15+
"plugins": [
16+
{
17+
"transform": "./src/index.ts",
18+
},
19+
{
20+
"transform": "./src/index.ts",
21+
"afterDeclarations": true
22+
}
23+
]
24+
}
25+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"references": [
3+
{
4+
"path": "./a"
5+
},
6+
{
7+
"path": "./b"
8+
}
9+
]
10+
}

test/tests/extras.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { createTsProgram, getEmitResultFromProgram } from "../utils";
2-
import { projectsPaths } from "./config";
2+
import { projectsPaths } from "../config";
33
import path from "path";
44
import ts from "typescript";
5-
import * as config from "./config";
5+
import * as config from "../config";
66
import { execSync } from "child_process";
77

88
/* ****************************************************************************************************************** *

test/tests/project-ref.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// noinspection ES6UnusedImports
2+
import {} from "ts-expose-internals";
3+
import * as path from "path";
4+
import { createTsSolutionBuilder, EmittedFiles } from "../utils";
5+
import { projectsPaths, ts } from "../config";
6+
7+
/* ****************************************************************************************************************** *
8+
* Config
9+
* ****************************************************************************************************************** */
10+
11+
/* File Paths */
12+
const projectDir = ts.normalizePath(path.join(projectsPaths, "project-ref"));
13+
const indexFile = ts.normalizePath(path.join(projectDir, "lib/b/index.ts"));
14+
15+
/* ****************************************************************************************************************** *
16+
* Tests
17+
* ****************************************************************************************************************** */
18+
19+
// see: https://github.com/LeDDGroup/typescript-transform-paths/issues/125
20+
describe(`Project References`, () => {
21+
let emittedFiles: EmittedFiles;
22+
23+
beforeAll(() => {
24+
const builder = createTsSolutionBuilder({ tsInstance: ts, projectDir });
25+
emittedFiles = builder.getEmitFiles();
26+
});
27+
28+
test(`Specifier for referenced project file resolves properly`, () => {
29+
expect(emittedFiles[indexFile].js).toMatch(`export { AReffedConst } from "../a/index"`);
30+
expect(emittedFiles[indexFile].dts).toMatch(`export { AReffedConst } from "../a/index"`);
31+
});
32+
33+
test(`Specifier for local file resolves properly`, () => {
34+
expect(emittedFiles[indexFile].js).toMatch(`export { LocalConst } from "./local/index"`);
35+
expect(emittedFiles[indexFile].dts).toMatch(`export { LocalConst } from "./local/index"`);
36+
});
37+
});

test/tests/transformer/general.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import {} from "ts-expose-internals";
33
import * as path from "path";
44
import { createTsProgram, EmittedFiles, getEmitResultFromProgram } from "../../utils";
5-
import { ts, tsModules, projectsPaths } from "../config";
5+
import { ts, tsModules, projectsPaths } from "../../config";
66

77
/* ****************************************************************************************************************** *
88
* Helpers

test/tests/transformer/specific.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import {
88
getManualEmitResult,
99
getTsNodeEmitResult,
1010
} from "../../utils";
11-
import { projectsPaths, ts, tsModules } from "../config";
12-
import { TsTransformPathsConfig } from '../../../src';
11+
import { projectsPaths, ts, tsModules } from "../../config";
12+
import { TsTransformPathsConfig } from "../../../src";
1313
import TS from "typescript";
1414

1515
/* ****************************************************************************************************************** *

test/utils/helpers.ts

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { default as tstpTransform, TsTransformPathsConfig } from "../../src";
22
import fs from "fs";
33
import ts from "typescript";
44
import * as tsNode from "ts-node";
5-
import * as config from "../tests/config";
5+
import * as config from "../config";
66

77
/* ****************************************************************************************************************** */
88
// region: Types
@@ -11,14 +11,49 @@ import * as config from "../tests/config";
1111
export type EmittedFiles = { [fileName: string]: { js: string; dts: string } };
1212

1313
export interface CreateTsProgramOptions {
14-
tsInstance: any;
14+
tsInstance: typeof ts;
1515
files?: { [fileName: string]: /* data */ string };
1616
tsConfigFile?: string;
1717
disablePlugin?: boolean;
1818
additionalOptions?: ts.CompilerOptions;
1919
pluginOptions?: TsTransformPathsConfig;
2020
}
2121

22+
export interface CreateTsSolutionBuilderOptions {
23+
tsInstance: typeof ts;
24+
projectDir: string;
25+
}
26+
27+
// endregion
28+
29+
/* ****************************************************************************************************************** */
30+
// region: Helpers
31+
/* ****************************************************************************************************************** */
32+
33+
function createWriteFile(outputFiles: EmittedFiles) {
34+
return (fileName: string, data: string) => {
35+
let { 1: rootName, 2: ext } = fileName.match(/(.+)\.((d.ts)|(js))$/) ?? [];
36+
if (!ext) return;
37+
rootName = `${rootName}.ts`;
38+
const key = ext.replace(".", "") as keyof EmittedFiles[string];
39+
if (!outputFiles[rootName]) outputFiles[rootName] = <any>{};
40+
outputFiles[rootName][key] = data;
41+
};
42+
}
43+
44+
function createReadFile(outputFiles: EmittedFiles, originalReadFile: Function) {
45+
return (fileName: string) => {
46+
let { 1: rootName, 2: ext } = fileName.match(/(.+)\.((d.ts)|(js))$/) ?? [];
47+
if (ext) {
48+
rootName = `${rootName}.ts`;
49+
const key = ext.replace(".", "") as keyof EmittedFiles[string];
50+
const res = outputFiles[rootName]?.[key];
51+
if (res) return res;
52+
}
53+
return originalReadFile(fileName);
54+
};
55+
}
56+
2257
// endregion
2358

2459
/* ****************************************************************************************************************** */
@@ -83,24 +118,36 @@ export function createTsProgram(
83118
return tsInstance.createProgram({ options: compilerOptions, rootNames: fileNames, host });
84119
}
85120

121+
export function createTsSolutionBuilder(
122+
opt: CreateTsSolutionBuilderOptions
123+
): ts.SolutionBuilder<ts.BuilderProgram> & { getEmitFiles(): EmittedFiles } {
124+
const { tsInstance, projectDir } = opt;
125+
126+
const outputFiles: EmittedFiles = {};
127+
128+
const host = tsInstance.createSolutionBuilderHost();
129+
const originalReadFile = host.readFile;
130+
Object.assign(host, {
131+
readFile: createReadFile(outputFiles, originalReadFile),
132+
writeFile: createWriteFile(outputFiles),
133+
});
134+
135+
const builder = tsInstance.createSolutionBuilder(host, [projectDir], { force: true });
136+
137+
return Object.assign(builder, {
138+
getEmitFiles() {
139+
builder.build();
140+
return outputFiles;
141+
},
142+
});
143+
}
144+
86145
/**
87146
* Get emitted files for program
88-
* @param program
89147
*/
90148
export function getEmitResultFromProgram(program: ts.Program): EmittedFiles {
91149
const outputFiles: EmittedFiles = {};
92-
93-
const writeFile = (fileName: string, data: string) => {
94-
let { 1: rootName, 2: ext } = fileName.match(/(.+)\.((d.ts)|(js))$/) ?? [];
95-
if (!ext) return;
96-
rootName = `${rootName}.ts`;
97-
const key = ext.replace(".", "") as keyof EmittedFiles[string];
98-
if (!outputFiles[rootName]) outputFiles[rootName] = <any>{};
99-
outputFiles[rootName][key] = data;
100-
};
101-
102-
program.emit(undefined, writeFile);
103-
150+
program.emit(undefined, createWriteFile(outputFiles));
104151
return outputFiles;
105152
}
106153

0 commit comments

Comments
 (0)