Skip to content

Commit 92fbd1c

Browse files
alicewriteswrongsrwaskiewicz
authored andcommitted
feat(testing): add support for transforming path aliases in spec tests (#4090)
This adds support for transforming path aliases in spec tests, using the transformer which was added in #4042. Adding such support also revealed a bug with the existing path transformer, notably that it did not produce a valid path for sibling modules. See the diff for a comment explaining the details.
1 parent 7c1a048 commit 92fbd1c

File tree

6 files changed

+149
-13
lines changed

6 files changed

+149
-13
lines changed

src/compiler/sys/typescript/typescript-sys.ts

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,15 @@ export const patchTsSystemFileSystem = (
5555
};
5656

5757
tsSys.directoryExists = (p) => {
58-
const s = inMemoryFs.statSync(p);
59-
return s.isDirectory;
58+
// At present the typing for `inMemoryFs` in this function is not accurate
59+
// TODO(STENCIL-728): fix typing of `inMemoryFs` parameter in `patchTypescript`, related functions
60+
if (inMemoryFs) {
61+
const s = inMemoryFs.statSync(p);
62+
return s.isDirectory;
63+
} else {
64+
const s = compilerSys.statSync(p);
65+
return s.isDirectory;
66+
}
6067
};
6168

6269
tsSys.exit = compilerSys.exit;
@@ -68,8 +75,15 @@ export const patchTsSystemFileSystem = (
6875
filePath = getTypescriptPathFromUrl(config, tsSys.getExecutingFilePath(), p);
6976
}
7077

71-
const s = inMemoryFs.statSync(filePath);
72-
return !!(s && s.isFile);
78+
// At present the typing for `inMemoryFs` in this function is not accurate
79+
// TODO(STENCIL-728): fix typing of `inMemoryFs` parameter in `patchTypescript`, related functions
80+
if (inMemoryFs) {
81+
const s = inMemoryFs.statSync(filePath);
82+
return !!(s && s.isFile);
83+
} else {
84+
const s = compilerSys.statSync(filePath);
85+
return !!(s && s.isFile);
86+
}
7387
};
7488

7589
tsSys.getCurrentDirectory = compilerSys.getCurrentDirectory;
@@ -79,8 +93,15 @@ export const patchTsSystemFileSystem = (
7993
tsSys.getDirectories = (p) => {
8094
const items = compilerSys.readDirSync(p);
8195
return items.filter((itemPath) => {
82-
const s = inMemoryFs.statSync(itemPath);
83-
return !!(s && s.exists && s.isDirectory);
96+
// At present the typing for `inMemoryFs` in this function is not accurate
97+
// TODO(STENCIL-728): fix typing of `inMemoryFs` parameter in `patchTypescript`, related functions
98+
if (inMemoryFs) {
99+
const s = inMemoryFs.statSync(itemPath);
100+
return !!(s && s.exists && s.isDirectory);
101+
} else {
102+
const s = compilerSys.statSync(itemPath);
103+
return !!(s && s.isDirectory);
104+
}
84105
});
85106
};
86107

@@ -108,7 +129,11 @@ export const patchTsSystemFileSystem = (
108129
filePath = getTypescriptPathFromUrl(config, tsSys.getExecutingFilePath(), p);
109130
}
110131

111-
let content = inMemoryFs.readFileSync(filePath, { useCache: isUrl });
132+
// At present the typing for `inMemoryFs` in this function is not accurate
133+
// TODO(STENCIL-728): fix typing of `inMemoryFs` parameter in `patchTypescript`, related functions
134+
let content = inMemoryFs
135+
? inMemoryFs.readFileSync(filePath, { useCache: isUrl })
136+
: compilerSys.readFileSync(filePath);
112137

113138
if (typeof content !== 'string' && isUrl) {
114139
if (IS_WEB_WORKER_ENV) {
@@ -124,7 +149,9 @@ export const patchTsSystemFileSystem = (
124149
return content;
125150
};
126151

127-
tsSys.writeFile = (p, data) => inMemoryFs.writeFile(p, data);
152+
// At present the typing for `inMemoryFs` in this function is not accurate
153+
// TODO(STENCIL-728): fix typing of `inMemoryFs` parameter in `patchTypescript`, related functions
154+
tsSys.writeFile = (p, data) => (inMemoryFs ? inMemoryFs.writeFile(p, data) : compilerSys.writeFile(p, data));
128155

129156
return tsSys;
130157
};

src/compiler/transformers/rewrite-aliased-paths.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,41 @@ function rewriteAliasedImport(
167167
node,
168168
retrieveTsModifiers(node),
169169
node.importClause,
170-
transformCtx.factory.createStringLiteral(importPath),
170+
transformCtx.factory.createStringLiteral(
171+
// if the importee is a sibling file of the importer then `relative` will
172+
// produce a somewhat confusing result. We use `dirname` to get the
173+
// directory of the importer, so for example, assume we have two files
174+
// `foo/bar.ts` and `foo/baz.ts` like so:
175+
//
176+
// ```
177+
// foo
178+
// ├── bar.ts
179+
// └── baz.ts
180+
// ```
181+
//
182+
// then if `baz.ts` imports a symbol from `bar.ts` we'll call
183+
// `relative(fromdir, to)` like so:
184+
//
185+
// ```ts
186+
// relative(dirname("foo/baz.ts"), "foo/bar.ts")
187+
// // equivalently
188+
// relative("foo", "foo/bar.ts")
189+
// ```
190+
//
191+
// you'd think that in this case `relative` would return `'./bar.ts'` as
192+
// a correct relative path to `bar.ts` from the `foo` directory, but
193+
// actually in this case it returns just `bar.ts`. So since when updating
194+
// import paths we're only concerned with `paths` aliases that should be
195+
// transformed to relative imports anyway, we check to see if the new
196+
// `importPath` starts with `'.'`, and add `'./'` if it doesn't, since
197+
// otherwise Node will interpret `bar` as a module name, not a relative
198+
// path.
199+
//
200+
// Note also that any relative paths as module specifiers which _don't_
201+
// need to be transformed (e.g. `'./foo'`) have already been handled
202+
// above.
203+
importPath.startsWith('.') ? importPath : './' + importPath
204+
),
171205
node.assertClause
172206
);
173207
}

src/compiler/transformers/test/rewrite-aliased-paths.spec.ts

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ import { transpileModule } from './transpile';
1515
* transpiles the provided code.
1616
*
1717
* @param component the string of a component
18+
* @param inputFileName an optional filename to use for the input file
1819
* @returns the tranpiled module
1920
*/
20-
async function pathTransformTranspile(component: string) {
21+
async function pathTransformTranspile(component: string, inputFileName = 'module.tsx') {
2122
const compilerContext: CompilerCtx = mockCompilerCtx();
2223
const config = mockValidatedConfig();
2324

@@ -37,8 +38,6 @@ async function pathTransformTranspile(component: string) {
3738
await compilerContext.fs.writeFile(path.join(config.rootDir, 'name/space.ts'), 'export const foo = x => x');
3839
await compilerContext.fs.writeFile(path.join(config.rootDir, 'name/space/subdir.ts'), 'export const bar = x => x;');
3940

40-
const inputFileName = normalizePath(path.join(config.rootDir, 'module.tsx'));
41-
4241
return transpileModule(
4342
component,
4443
null,
@@ -47,7 +46,7 @@ async function pathTransformTranspile(component: string) {
4746
[],
4847
[rewriteAliasedDTSImportPaths],
4948
mockPathsConfig,
50-
inputFileName
49+
normalizePath(path.join(config.rootDir, inputFileName))
5150
);
5251
}
5352

@@ -144,4 +143,63 @@ describe('rewrite alias module paths transform', () => {
144143
'import { Foo } from "./name/space/subdir";import { Bar } from "./name/space";export declare function fooUtil(foo: Foo): Bar;'
145144
);
146145
});
146+
147+
it('should correctly rewrite sibling paths', async () => {
148+
const t = await pathTransformTranspile(
149+
`
150+
import { foo } from "@namespace";
151+
export class CmpA {
152+
render() {
153+
return <some-cmp>{ foo("bar") }</some-cmp>
154+
}
155+
}
156+
`,
157+
'name/component.tsx'
158+
);
159+
160+
// with the import filename passed to `pathTransformTranspile` the file
161+
// layout during this test looks like this:
162+
//
163+
// ```
164+
// name
165+
// ├── space.ts
166+
// └── component.tsx
167+
// ```
168+
//
169+
// we need to test that the relative path from `name/component.tsx` to
170+
// `name/space.ts` is resolved correctly as `'./space'`.
171+
expect(t.outputText).toBe(
172+
'import { foo } from "./space";export class CmpA { render() { return h("some-cmp", null, foo("bar")); }}'
173+
);
174+
});
175+
176+
it('should correctly rewrite nested sibling paths', async () => {
177+
const t = await pathTransformTranspile(
178+
`
179+
import { foo } from "@namespace/subdir";
180+
export class CmpA {
181+
render() {
182+
return <some-cmp>{ foo("bar") }</some-cmp>
183+
}
184+
}
185+
`,
186+
'name/component.tsx'
187+
);
188+
189+
// with the import filename passed to `pathTransformTranspile` the file
190+
// layout during this test looks like this:
191+
//
192+
// ```
193+
// name
194+
// ├── component.tsx
195+
// └── space
196+
// └── subdir.ts
197+
// ```
198+
//
199+
// we need to test that the relative path from `name/component.tsx` to
200+
// `name/space/subdir.ts` is resolved correctly as `'./space/subdir'`.
201+
expect(t.outputText).toBe(
202+
'import { foo } from "./space/subdir";export class CmpA { render() { return h("some-cmp", null, foo("bar")); }}'
203+
);
204+
});
147205
});

src/declarations/stencil-private.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2299,6 +2299,11 @@ export interface E2EProcessEnv {
22992299
__STENCIL_PUPPETEER_MODULE__?: string;
23002300
__STENCIL_PUPPETEER_VERSION__?: number;
23012301
__STENCIL_DEFAULT_TIMEOUT__?: string;
2302+
2303+
/**
2304+
* Property for injecting transformAliasedImportPaths into the Jest context
2305+
*/
2306+
__STENCIL_TRANSPILE_PATHS__?: 'true' | 'false';
23022307
}
23032308

23042309
export interface AnyHTMLElement extends HTMLElement {

src/testing/jest/jest-runner.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export async function runJest(config: d.ValidatedConfig, env: d.E2EProcessEnv) {
1313
const emulateConfigs = getEmulateConfigs(config.testing, config.flags);
1414
env.__STENCIL_EMULATE_CONFIGS__ = JSON.stringify(emulateConfigs);
1515
env.__STENCIL_ENV__ = JSON.stringify(config.env);
16+
env.__STENCIL_TRANSPILE_PATHS__ = config.transformAliasedImportPaths ? 'true' : 'false';
1617

1718
if (config.flags.ci || config.flags.e2e) {
1819
env.__STENCIL_DEFAULT_TIMEOUT__ = '30000';

src/testing/test-transpile.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export function transpile(input: string, opts: TranspileOptions = {}): Transpile
1515
style: null,
1616
styleImportData: 'queryparams',
1717
target: 'es2015', // default to es2015
18+
transformAliasedImportPaths: parseStencilTranspilePaths(process.env.__STENCIL_TRANSPILE_PATHS__),
1819
};
1920

2021
try {
@@ -27,3 +28,13 @@ export function transpile(input: string, opts: TranspileOptions = {}): Transpile
2728

2829
return transpileSync(input, opts);
2930
}
31+
32+
/**
33+
* Turn a value which we assert can be 'true' or 'false' to a boolean.
34+
*
35+
* @param stencilTranspilePaths a value to 'parse'
36+
* @returns a boolean
37+
*/
38+
function parseStencilTranspilePaths(stencilTranspilePaths: string): boolean {
39+
return stencilTranspilePaths === 'true' ? true : false;
40+
}

0 commit comments

Comments
 (0)