Skip to content

Commit 0c65664

Browse files
Alanmgechev
Alan
authored andcommitted
fix(@angular-devkit/build-angular): error only when file is not found in non of the programs
At the moment, if a user provides multiple tsconfig, a file needs to be part of all compilations as otherwise it will fail. This PR changes this behavour and as long as it's in one of the compilations it will not error out. Fixes #13399
1 parent e12adf4 commit 0c65664

File tree

2 files changed

+56
-22
lines changed

2 files changed

+56
-22
lines changed

packages/angular_devkit/build_angular/src/tslint/index.ts

+23-21
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,12 @@ export default class TslintBuilder implements Builder<TslintBuilderOptions> {
8484
let result: undefined | tslint.LintResult;
8585
if (options.tsConfig) {
8686
const tsConfigs = Array.isArray(options.tsConfig) ? options.tsConfig : [options.tsConfig];
87+
const allPrograms =
88+
tsConfigs.map(tsConfig => Linter.createProgram(path.resolve(systemRoot, tsConfig)));
8789

88-
for (const tsConfig of tsConfigs) {
89-
const program = Linter.createProgram(path.resolve(systemRoot, tsConfig));
90-
const partial = lint(projectTslint, systemRoot, tslintConfigPath, options, program);
90+
for (const program of allPrograms) {
91+
const partial
92+
= lint(projectTslint, systemRoot, tslintConfigPath, options, program, allPrograms);
9193
if (result == undefined) {
9294
result = partial;
9395
} else {
@@ -152,6 +154,7 @@ function lint(
152154
tslintConfigPath: string | null,
153155
options: TslintBuilderOptions,
154156
program?: ts.Program,
157+
allPrograms?: ts.Program[],
155158
) {
156159
const Linter = projectTslint.Linter;
157160
const Configuration = projectTslint.Configuration;
@@ -167,7 +170,21 @@ function lint(
167170
let lastDirectory;
168171
let configLoad;
169172
for (const file of files) {
170-
const contents = getFileContents(file, options, program);
173+
let contents = '';
174+
if (program && allPrograms) {
175+
if (!program.getSourceFile(file)) {
176+
if (!allPrograms.some(p => p.getSourceFile(file) !== undefined)) {
177+
// File is not part of any typescript program
178+
throw new Error(
179+
`File '${file}' is not part of a TypeScript project '${options.tsConfig}'.`);
180+
}
181+
182+
// if the Source file exists but it's not in the current program skip
183+
continue;
184+
}
185+
} else {
186+
contents = getFileContents(file);
187+
}
171188

172189
// Only check for a new tslint config if the path changes.
173190
const currentDirectory = path.dirname(file);
@@ -176,7 +193,7 @@ function lint(
176193
lastDirectory = currentDirectory;
177194
}
178195

179-
if (contents && configLoad) {
196+
if (configLoad) {
180197
linter.lint(file, contents, configLoad.results);
181198
}
182199
}
@@ -217,22 +234,7 @@ function getFilesToLint(
217234
return programFiles;
218235
}
219236

220-
function getFileContents(
221-
file: string,
222-
options: TslintBuilderOptions,
223-
program?: ts.Program,
224-
): string | undefined {
225-
// The linter retrieves the SourceFile TS node directly if a program is used
226-
if (program) {
227-
if (program.getSourceFile(file) == undefined) {
228-
const message = `File '${file}' is not part of the TypeScript project '${options.tsConfig}'.`;
229-
throw new Error(message);
230-
}
231-
232-
// TODO: this return had to be commented out otherwise no file would be linted, figure out why.
233-
// return undefined;
234-
}
235-
237+
function getFileContents(file: string): string {
236238
// NOTE: The tslint CLI checks for and excludes MPEG transport streams; this does not.
237239
try {
238240
return stripBom(readFileSync(file, 'utf-8'));

packages/angular_devkit/build_angular/test/tslint/works_spec_large.ts

+33-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
import { DefaultTimeout, TestLogger, runTargetSpec } from '@angular-devkit/architect/testing';
1010
import { normalize, virtualFs } from '@angular-devkit/core';
11-
import { tap } from 'rxjs/operators';
11+
import { EMPTY } from 'rxjs';
12+
import { catchError, tap } from 'rxjs/operators';
1213
import { TslintBuilderOptions } from '../../src';
1314
import { host, tslintTargetSpec } from '../utils';
1415

@@ -215,6 +216,37 @@ describe('Tslint Target', () => {
215216
).toPromise().then(done, done.fail);
216217
}, 30000);
217218

219+
it('works when a file is only part of one project when using two program', (done) => {
220+
const overrides: Partial<TslintBuilderOptions> = {
221+
tsConfig: ['src/tsconfig.app.json', 'src/tsconfig.spec.json'],
222+
files: ['src/foo/foo.component.ts'],
223+
};
224+
225+
host.writeMultipleFiles({ 'src/foo/foo.component.ts': `const foo = '';\n` });
226+
227+
runTargetSpec(host, tslintTargetSpec, overrides).pipe(
228+
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
229+
).toPromise().then(done, done.fail);
230+
}, 30000);
231+
232+
it('errors when file is not part of any typescript program', (done) => {
233+
const overrides: Partial<TslintBuilderOptions> = {
234+
tsConfig: ['src/tsconfig.spec.json'],
235+
files: ['src/foo/foo.component.ts'],
236+
};
237+
238+
host.writeMultipleFiles({ 'src/foo/foo.component.ts': `const foo = '';\n` });
239+
240+
runTargetSpec(host, tslintTargetSpec, overrides).pipe(
241+
tap((buildEvent) => expect(buildEvent.success).toBe(false)),
242+
catchError((err) => {
243+
expect(err).toMatch(`foo.component.ts' is not part of a TypeScript project`);
244+
245+
return EMPTY;
246+
}),
247+
).toPromise().then(done, done.fail);
248+
}, 30000);
249+
218250
it('errors when type checking is used without a project', (done) => {
219251
const overrides: Partial<TslintBuilderOptions> = {
220252
tsConfig: undefined,

0 commit comments

Comments
 (0)