Skip to content

Commit

Permalink
fix(compiler): respect project tsconfig watch options (#5916)
Browse files Browse the repository at this point in the history
* fix(compiler): respect project tsconfig watch options

Fixes: #5709

STENCIL-1079

* add tests

* export function
  • Loading branch information
tanner-reits authored Aug 1, 2024
1 parent fd75952 commit 74adeee
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/compiler/config/load-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export const loadConfig = async (init: LoadConfigInit = {}): Promise<LoadConfigR

results.config.tsconfig = tsConfigResults.path;
results.config.tsCompilerOptions = tsConfigResults.compilerOptions;
results.config.tsWatchOptions = tsConfigResults.watchOptions;

results.tsconfig.path = tsConfigResults.path;
results.tsconfig.compilerOptions = JSON.parse(JSON.stringify(tsConfigResults.compilerOptions));
Expand Down
65 changes: 58 additions & 7 deletions src/compiler/sys/typescript/tests/typescript-config.spec.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
import { hasSrcDirectoryInclude } from '../typescript-config';
import ts from 'typescript';

import { ValidatedConfig } from '../../../../declarations';
import { mockValidatedConfig } from '../../../../testing/mocks';
import { createTestingSystem, TestingSystem } from '../../../../testing/testing-sys';
import * as tsConfig from '../typescript-config';

describe('typescript-config', () => {
describe('hasSrcDirectoryInclude', () => {
it('returns `false` for a non-array argument', () => {
// the intent of this test is to evaluate when a user doesn't provide an array, hence the type assertion
expect(hasSrcDirectoryInclude('src' as unknown as string[], 'src')).toBe(false);
expect(tsConfig.hasSrcDirectoryInclude('src' as unknown as string[], 'src')).toBe(false);
});

it('returns `false` for an empty array', () => {
expect(hasSrcDirectoryInclude([], 'src/')).toBe(false);
expect(tsConfig.hasSrcDirectoryInclude([], 'src/')).toBe(false);
});

it('returns `false` when an entry does not exist in the array', () => {
expect(hasSrcDirectoryInclude(['src'], 'source')).toBe(false);
expect(tsConfig.hasSrcDirectoryInclude(['src'], 'source')).toBe(false);
});

it('returns `true` when an entry does exist in the array', () => {
expect(hasSrcDirectoryInclude(['src', 'foo'], 'src')).toBe(true);
expect(tsConfig.hasSrcDirectoryInclude(['src', 'foo'], 'src')).toBe(true);
});

it('returns `true` for globs', () => {
expect(hasSrcDirectoryInclude(['src/**/*.ts', 'foo/'], 'src/**/*.ts')).toBe(true);
expect(tsConfig.hasSrcDirectoryInclude(['src/**/*.ts', 'foo/'], 'src/**/*.ts')).toBe(true);
});

it.each([
Expand All @@ -29,7 +34,53 @@ describe('typescript-config', () => {
[['../src'], '../src'],
[['*'], './*'],
])('returns `true` for relative paths', (includedPaths, srcDir) => {
expect(hasSrcDirectoryInclude(includedPaths, srcDir)).toBe(true);
expect(tsConfig.hasSrcDirectoryInclude(includedPaths, srcDir)).toBe(true);
});
});

describe('validateTsConfig', () => {
let mockSys: TestingSystem;
let config: ValidatedConfig;
let tsSpy: jest.SpyInstance;

beforeEach(() => {
mockSys = createTestingSystem();
config = mockValidatedConfig();

jest.spyOn(tsConfig, 'getTsConfigPath').mockResolvedValue({
path: 'tsconfig.json',
content: '',
});
tsSpy = jest.spyOn(ts, 'getParsedCommandLineOfConfigFile');
});

it('includes watchOptions when provided', async () => {
tsSpy.mockReturnValueOnce({
watchOptions: {
excludeFiles: ['exclude.ts'],
excludeDirectories: ['exclude-dir'],
},
options: null,
fileNames: [],
errors: [],
});

const result = await tsConfig.validateTsConfig(config, mockSys, {});
expect(result.watchOptions).toEqual({
excludeFiles: ['exclude.ts'],
excludeDirectories: ['exclude-dir'],
});
});

it('does not include watchOptions when not provided', async () => {
tsSpy.mockReturnValueOnce({
options: null,
fileNames: [],
errors: [],
});

const result = await tsConfig.validateTsConfig(config, mockSys, {});
expect(result.watchOptions).toEqual({});
});
});
});
7 changes: 6 additions & 1 deletion src/compiler/sys/typescript/typescript-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const validateTsConfig = async (config: d.ValidatedConfig, sys: d.Compile
const tsconfig = {
path: '',
compilerOptions: {} as ts.CompilerOptions,
watchOptions: {} as ts.WatchOptions,
files: [] as string[],
include: [] as string[],
exclude: [] as string[],
Expand Down Expand Up @@ -91,6 +92,10 @@ export const validateTsConfig = async (config: d.ValidatedConfig, sys: d.Compile
}
}

if (results.watchOptions) {
tsconfig.watchOptions = results.watchOptions;
}

if (results.options) {
tsconfig.compilerOptions = results.options;

Expand Down Expand Up @@ -119,7 +124,7 @@ export const validateTsConfig = async (config: d.ValidatedConfig, sys: d.Compile
return tsconfig;
};

const getTsConfigPath = async (
export const getTsConfigPath = async (
config: d.ValidatedConfig,
sys: d.CompilerSystem,
init: d.LoadConfigInit,
Expand Down
9 changes: 9 additions & 0 deletions src/compiler/transpile/create-watch-program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ export const createTsWatchProgram = async (
(reportWatchStatus) => {
config.logger.debug(reportWatchStatus.messageText);
},
// We don't want to allow users to mess with the watch method, so
// we only strip out the excludeFiles and excludeDirectories properties
// to allow the user to still have control over which files get excluded from the watcher
config.tsWatchOptions
? {
excludeFiles: config.tsWatchOptions.excludeFiles,
excludeDirectories: config.tsWatchOptions.excludeDirectories,
}
: undefined,
);

// Add a callback that will execute whenever a new instance
Expand Down
46 changes: 46 additions & 0 deletions src/compiler/transpile/test/create-watch-program.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import ts from 'typescript';

import { ValidatedConfig } from '../../../declarations';
import { mockValidatedConfig } from '../../../testing/mocks';
import { createTsWatchProgram } from '../create-watch-program';

describe('createWatchProgram', () => {
let config: ValidatedConfig;

beforeEach(() => {
config = mockValidatedConfig();
});

afterEach(() => {
jest.restoreAllMocks();
});

it('includes watchOptions in the watch program creation', async () => {
config.tsWatchOptions = {
fallbackPolling: 3,
excludeFiles: ['src/components/my-component/my-component.tsx'],
excludeDirectories: ['src/components/my-other-component'],
} as ts.WatchOptions;
config.tsconfig = '';
const tsSpy = jest.spyOn(ts, 'createWatchCompilerHost').mockReturnValue({} as any);
jest.spyOn(ts, 'createWatchProgram').mockReturnValue({} as any);

await createTsWatchProgram(config, () => new Promise(() => {}));

expect(tsSpy.mock.calls[0][6]).toEqual({
excludeFiles: ['src/components/my-component/my-component.tsx'],
excludeDirectories: ['src/components/my-other-component'],
});
});

it('omits watchOptions when not provided', async () => {
config.tsWatchOptions = undefined;
config.tsconfig = '';
const tsSpy = jest.spyOn(ts, 'createWatchCompilerHost').mockReturnValue({} as any);
jest.spyOn(ts, 'createWatchProgram').mockReturnValue({} as any);

await createTsWatchProgram(config, () => new Promise(() => {}));

expect(tsSpy.mock.calls[0][6]).toEqual(undefined);
});
});
1 change: 1 addition & 0 deletions src/declarations/stencil-public-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ export interface Config extends StencilConfig {
suppressLogs?: boolean;
profile?: boolean;
tsCompilerOptions?: any;
tsWatchOptions?: any;
_isValidated?: boolean;
_isTesting?: boolean;
}
Expand Down

0 comments on commit 74adeee

Please sign in to comment.