Skip to content

Commit

Permalink
fix: symlinks to not be resolved to real path (#75)
Browse files Browse the repository at this point in the history
  • Loading branch information
inga-lovinde authored May 6, 2024
1 parent 3b01d6a commit b009299
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 14 deletions.
17 changes: 7 additions & 10 deletions src/parse-tsconfig/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import slash from 'slash';
import type { TsConfigJson, TsConfigJsonResolved, Cache } from '../types.js';
import { normalizePath } from '../utils/normalize-path.js';
import { readJsonc } from '../utils/read-jsonc.js';
import { realpath } from '../utils/fs-cached.js';
import { implicitBaseUrlSymbol } from '../utils/symbols.js';
import { resolveExtendsPath } from './resolve-extends-path.js';

Expand Down Expand Up @@ -82,13 +81,6 @@ const _parseTsconfig = (
cache?: Cache<string>,
circularExtendsTracker = new Set<string>(),
): TsConfigJsonResolved => {
let realTsconfigPath: string;
try {
realTsconfigPath = realpath(cache, tsconfigPath) as string;
} catch {
throw new Error(`Cannot resolve tsconfig at path: ${tsconfigPath}`);
}

/**
* Decided not to cache the TsConfigJsonResolved object because it's
* mutable.
Expand All @@ -99,13 +91,18 @@ const _parseTsconfig = (
*
* By only caching fs results, we can avoid serving mutated objects
*/
let config: TsConfigJson = readJsonc(realTsconfigPath, cache) || {};
let config: TsConfigJson;
try {
config = readJsonc(tsconfigPath, cache) || {};
} catch {
throw new Error(`Cannot resolve tsconfig at path: ${tsconfigPath}`);
}

if (typeof config !== 'object') {
throw new SyntaxError(`Failed to parse tsconfig at: ${tsconfigPath}`);
}

const directoryPath = path.dirname(realTsconfigPath);
const directoryPath = path.dirname(tsconfigPath);

if (config.compilerOptions) {
const { compilerOptions } = config;
Expand Down
1 change: 0 additions & 1 deletion src/utils/fs-cached.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,5 @@ const cacheFs = <MethodName extends keyof FsMethods>(
};

export const exists = cacheFs('existsSync');
export const realpath = cacheFs('realpathSync');
export const readFile = cacheFs('readFileSync');
export const stat = cacheFs('statSync');
2 changes: 1 addition & 1 deletion tests/specs/get-tsconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export default testSuite(({ describe }) => {
const cache = new Map();
const tsconfig = getTsconfig(fixture.path, 'tsconfig.json', cache);
expect(tsconfig).toStrictEqual(expectedResult);
expect(cache.size).toBe(3);
expect(cache.size).toBe(2);

await fixture.rm('tsconfig.json');

Expand Down
60 changes: 60 additions & 0 deletions tests/specs/parse-tsconfig/extends/merges.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fs from 'fs/promises';
import path from 'path';
import { testSuite, expect } from 'manten';
import { createFixture } from 'fs-fixture';
Expand Down Expand Up @@ -220,6 +221,39 @@ export default testSuite(({ describe }) => {

expect(tsconfig).toStrictEqual(expectedTsconfig);
});

test('inherits from symlinked configs', async () => {
await using fixture = await createFixture({
'symlink-source': {
'tsconfig.base.json': createTsconfigJson({
include: ['../src/*'],
}),
},
project: {
src: {
'a.ts': '',
'b.ts': '',
'c.ts': '',
},
'tsconfig.json': createTsconfigJson({
extends: './symlink/tsconfig.base.json',
}),
},
});

await fs.symlink(fixture.getPath('symlink-source'), fixture.getPath('project/symlink'));

const expectedTsconfig = await getTscTsconfig(path.join(fixture.path, 'project'));
delete expectedTsconfig.files;

const tsconfig = parseTsconfig(path.join(fixture.path, 'project', 'tsconfig.json'));

expect({
...tsconfig,
// See https://github.com/privatenumber/get-tsconfig/issues/73
include: tsconfig.include?.map(includePath => `symlink/../${includePath}`),
}).toStrictEqual(expectedTsconfig);
});
});

describe('baseUrl', ({ test }) => {
Expand Down Expand Up @@ -309,6 +343,32 @@ export default testSuite(({ describe }) => {
const tsconfig = parseTsconfig(path.join(fixture.path, 'tsconfig.json'));
expect(tsconfig).toStrictEqual(expectedTsconfig);
});

test('resolves parent baseUrl path defined in symlinked config', async () => {
await using fixture = await createFixture({
'symlink-source': {
'tsconfig.json': createTsconfigJson({
compilerOptions: {
baseUrl: '..',
},
}),
},
project: {
'tsconfig.json': createTsconfigJson({
extends: './symlink/tsconfig.json',
}),
'a.ts': '',
},
});

await fs.symlink(fixture.getPath('symlink-source'), fixture.getPath('project/symlink'));

const expectedTsconfig = await getTscTsconfig(path.join(fixture.path, 'project'));
delete expectedTsconfig.files;

const tsconfig = parseTsconfig(path.join(fixture.path, 'project', 'tsconfig.json'));
expect(tsconfig).toStrictEqual(expectedTsconfig);
});
});

test('nested extends', async () => {
Expand Down
1 change: 1 addition & 0 deletions tests/specs/parse-tsconfig/extends/resolves/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,6 @@ export default testSuite(({ describe }) => {
runTestSuite(import('./relative-path.spec.js'));
runTestSuite(import('./absolute-path.spec.js'));
runTestSuite(import('./node-modules.spec.js'));
runTestSuite(import('./symbolic-link.spec.js'));
});
});
122 changes: 122 additions & 0 deletions tests/specs/parse-tsconfig/extends/resolves/symbolic-link.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import fs from 'fs/promises';
import path from 'path';
import { testSuite, expect } from 'manten';
import { createFixture } from 'fs-fixture';
import { createTsconfigJson, getTscTsconfig } from '../../../../utils.js';
import { parseTsconfig } from '#get-tsconfig';

const validate = async (directoryPath: string) => {
const expectedTsconfig = await getTscTsconfig(directoryPath);
delete expectedTsconfig.files;

const tsconfig = parseTsconfig(path.join(directoryPath, 'tsconfig.json'));
expect(tsconfig).toStrictEqual(expectedTsconfig);
};

export default testSuite(({ describe }) => {
describe('symbolic link', ({ test }) => {
test('extends symlink to file', async () => {
await using fixture = await createFixture({
'tsconfig.symlink-source.json': createTsconfigJson({
compilerOptions: {
jsx: 'react',
allowJs: true,
},
}),
'tsconfig.json': createTsconfigJson({
extends: './tsconfig.symlink.json',
compilerOptions: {
strict: true,
},
}),
'file.ts': '',
});

await fs.symlink(fixture.getPath('tsconfig.symlink-source.json'), fixture.getPath('tsconfig.symlink.json'));

await validate(fixture.path);
});

test('extends file from symlink to directory', async () => {
await using fixture = await createFixture({
'symlink-source': {
'tsconfig.json': createTsconfigJson({
compilerOptions: {
jsx: 'react',
allowJs: true,
},
}),
},
'tsconfig.json': createTsconfigJson({
extends: './symlink/tsconfig.json',
compilerOptions: {
strict: true,
},
}),
'file.ts': '',
});

await fs.symlink(fixture.getPath('symlink-source'), fixture.getPath('symlink'));

await validate(fixture.path);
});

test('extends from symlink to file in origin directory', async () => {
await using fixture = await createFixture({
'symlink-source': {
'tsconfig.main.json': createTsconfigJson({
extends: './tsconfig.base.json',
compilerOptions: {
strict: true,
},
}),
},
project: {
'tsconfig.base.json': createTsconfigJson({
compilerOptions: {
jsx: 'react',
allowJs: true,
},
}),
'file.ts': '',
},
});

await fs.symlink(fixture.getPath('symlink-source/tsconfig.main.json'), fixture.getPath('project/tsconfig.json'));

await validate(fixture.getPath('project'));
});

test('extends from file in symlinked directory to file in origin directory', async () => {
await using fixture = await createFixture({
'symlink-source': {
'tsconfig.main.json': createTsconfigJson({
extends: '../tsconfig.base.json',
compilerOptions: {
strict: true,
},
}),
},
project: {
'tsconfig.base.json': createTsconfigJson({
compilerOptions: {
jsx: 'react',
allowJs: true,
},
}),
'tsconfig.json': createTsconfigJson({
extends: './symlink/tsconfig.main.json',
compilerOptions: {
importHelpers: true,
},
}),
'file.ts': '',
},
});

await fs.symlink(fixture.getPath('symlink-source'), fixture.getPath('project/symlink'));

await validate(fixture.getPath('project'));
});
});
});
4 changes: 2 additions & 2 deletions tests/specs/parse-tsconfig/parses.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,15 @@ export default testSuite(({ describe }) => {

const cache = new Map();
const parsedTsconfig = parseTsconfig(path.join(fixture.path, 'tsconfig.json'), cache);
expect(cache.size).toBe(2);
expect(cache.size).toBe(1);

const expectedTsconfig = await getTscTsconfig(fixture.path);
delete expectedTsconfig.files;

expect(parsedTsconfig).toStrictEqual(expectedTsconfig);

const parsedTsconfigCached = parseTsconfig(path.join(fixture.path, 'tsconfig.json'), cache);
expect(cache.size).toBe(2);
expect(cache.size).toBe(1);

expect(parsedTsconfigCached).toStrictEqual(expectedTsconfig);
});
Expand Down

0 comments on commit b009299

Please sign in to comment.