Skip to content

Commit 99cf4be

Browse files
authored
Merge pull request #131 from benyap/fix/non-idempotent-behaviour-for-wildcard-paths
fix: do not replace existing relative paths
2 parents cce8c94 + 8b1f4b7 commit 99cf4be

File tree

9 files changed

+67
-16
lines changed

9 files changed

+67
-16
lines changed

README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ Use this package after `tsc` builds your code to replace any path aliases with
1616
relative paths - this means that you can develop using path aliases whilst still
1717
being able to ship working JavaScript code.
1818

19-
_Yes, there are plugins that can handle this when you use bundlers such as
20-
Webpack or Rollup. But if you don't want to use a bundler, this package is a
21-
convenient solution._
22-
2319
**Sample `tsconfig.json`:**
2420

2521
```ts

src/steps/computeAliases.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { resolve } from "path";
22

33
import type { Alias } from "~/types";
4+
import { InvalidAliasError } from "~/utils/errors";
45

56
/**
67
* Compute the alias paths provided by the tsconfig.
@@ -10,11 +11,21 @@ export function computeAliases(
1011
paths: { [key: string]: string[] }
1112
): Alias[] {
1213
const regex = /\*$/;
14+
1315
const aliases: Alias[] = Object.keys(paths).map((alias) => ({
16+
alias,
1417
prefix: alias.replace(regex, ""),
1518
aliasPaths: paths[alias].map((path: string) =>
1619
resolve(basePath, path.replace(regex, ""))
1720
),
1821
}));
22+
23+
// Ensure that aliases do not start with a relative path
24+
// This will lead to unknown behaviour, and why would you use ./ or ../ as an alias anyway?
25+
for (const { alias } of aliases) {
26+
if (alias.startsWith("./") || alias.startsWith("../"))
27+
throw new InvalidAliasError(computeAliases.name, alias);
28+
}
29+
1930
return aliases;
2031
}

src/steps/generateChanges.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,15 @@ export function aliasToRelativePath(
120120
): { file: string; original: string; replacement?: string } {
121121
const { srcPath, outPath } = programPaths;
122122

123+
// Ignore any relative paths and return the original path
124+
// ASSUMPTION: they are either not an alias, or have already been replaced
125+
if (path.startsWith("./") || path.startsWith("../"))
126+
return { file: filePath, original: path };
127+
123128
for (const alias of aliases) {
124129
const { prefix, aliasPaths } = alias;
125130

126-
// Skip the alias if the path does not start with it
131+
// Skip the alias if the path does not start with the prefix
127132
if (!path.startsWith(prefix)) continue;
128133

129134
const pathRelative = path.substring(prefix.length);

src/steps/loadTSConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { FileNotFoundError } from "~/utils/errors";
1515
*/
1616
export function loadTSConfig(path: string): TSConfig {
1717
const configFileName = findConfigFile(process.cwd(), sys.fileExists, path);
18-
if (!configFileName) throw new FileNotFoundError("loadTSConfig", path);
18+
if (!configFileName) throw new FileNotFoundError(loadTSConfig.name, path);
1919
const configFile = readConfigFile(configFileName, sys.readFile);
2020
const options = parseJsonConfigFileContent(configFile.config, sys, ".");
2121
return options;

src/steps/resolvePaths.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@ export function resolvePaths(
2828
);
2929

3030
if (!paths)
31-
throw new TSConfigPropertyError(
32-
"resolveConfigPaths",
33-
"compilerOptions.paths"
34-
);
31+
throw new TSConfigPropertyError(resolvePaths.name, "compilerOptions.paths");
3532

3633
const configFile = resolve(process.cwd(), options.project);
3734
const configPath = dirname(configFile);

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export interface ProgramPaths {
5151
}
5252

5353
export interface Alias {
54+
/** The original path alias. */
55+
alias: string;
5456
/** The alias prefix that has been matched. */
5557
prefix: string;
5658
/** The paths that the alias points to. */

src/utils/errors.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ export class FileNotFoundError extends FileError {
2020
}
2121
}
2222

23-
export class JSONFileParsingError extends FileError {
24-
constructor(step: string, path: string, message: string) {
25-
super(step, path, `Failed to parse JSON. ${message}`);
26-
}
27-
}
28-
2923
export class TSConfigPropertyError extends StepError {
3024
constructor(public readonly step: string, public readonly property: string) {
3125
super(step, `${property} is not set in tsconfig`);
3226
}
3327
}
28+
29+
export class InvalidAliasError extends StepError {
30+
constructor(public readonly step: string, public readonly alias: string) {
31+
super(step, `The alias ${alias} is not permitted`);
32+
}
33+
}

test/steps/computeAliases.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { resolve } from "path";
22

33
import { computeAliases } from "~/steps/computeAliases";
4+
import { InvalidAliasError } from "~/utils/errors";
45

56
describe("steps/computeAliases", () => {
67
it("computes aliases correctly from the root path", () => {
@@ -38,4 +39,24 @@ describe("steps/computeAliases", () => {
3839
expect(aliases[1].aliasPaths).toEqual([`${cwd}/src`, `${cwd}/root`]);
3940
expect(aliases[2].aliasPaths).toEqual([`${cwd}/src/app`]);
4041
});
42+
43+
it("throws an error if a path alias starting with ./ is detected", () => {
44+
const attempt = () =>
45+
computeAliases(resolve("."), {
46+
"./*": ["./lib/*"],
47+
"~/*": ["./src/*", "./root/*"],
48+
"@app": ["./src/app/*"],
49+
});
50+
expect(attempt).toThrowError(InvalidAliasError);
51+
});
52+
53+
it("throws an error if a path alias starting with ../ is detected", () => {
54+
const attempt = () =>
55+
computeAliases(resolve("."), {
56+
"../*": ["./lib/*"],
57+
"~/*": ["./src/*", "./root/*"],
58+
"@app": ["./src/app/*"],
59+
});
60+
expect(attempt).toThrowError(InvalidAliasError);
61+
});
4162
});

test/steps/generateChanges.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ describe("steps/generateChanges", () => {
168168
const root = `${cwd}/test/fixtures/change`;
169169
const aliases: Alias[] = [
170170
{
171+
alias: "~/*",
171172
prefix: "~/",
172173
aliasPaths: [`${root}/src`, `${root}/src/alternateSrc`],
173174
},
@@ -277,6 +278,22 @@ describe("steps/generateChanges", () => {
277278
`);
278279
});
279280

281+
it("does not replace paths that are already relative", () => {
282+
const result = aliasToRelativePath(
283+
"../..",
284+
"test/fixtures/change/out/nested/imports.js",
285+
[{ alias: "*", prefix: "", aliasPaths: [`${root}/src`] }],
286+
programPaths
287+
);
288+
289+
expect(result).toMatchInlineSnapshot(`
290+
Object {
291+
"file": "test/fixtures/change/out/nested/imports.js",
292+
"original": "../..",
293+
}
294+
`);
295+
});
296+
280297
it("returns the correct relative path for an aliased path from a nested directory using a secondary alias", () => {
281298
const result = aliasToRelativePath(
282299
"~/alternate",
@@ -300,6 +317,7 @@ describe("steps/generateChanges", () => {
300317
const root = `${cwd}/test/fixtures/change`;
301318
const aliases: Alias[] = [
302319
{
320+
alias: "~/*",
303321
prefix: "~/",
304322
aliasPaths: [`${root}/src`, `${root}/src/alternateSrc`],
305323
},
@@ -495,6 +513,7 @@ describe("steps/generateChanges", () => {
495513
const root = `${cwd}/test/fixtures/change`;
496514
const aliases: Alias[] = [
497515
{
516+
alias: "~/*",
498517
prefix: "~/",
499518
aliasPaths: [`${root}/src`, `${root}/src/alternateSrc`],
500519
},

0 commit comments

Comments
 (0)