Skip to content

Commit 8a0f92b

Browse files
filipesilvamgechev
authored andcommitted
fix(@ngtools/webpack): recursive look up unused files
Fix #15626
1 parent 9b91163 commit 8a0f92b

File tree

2 files changed

+56
-26
lines changed

2 files changed

+56
-26
lines changed

packages/angular_devkit/build_angular/test/browser/unused-files-warning_spec_large.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,36 @@ describe('Browser Builder unused files warnings', () => {
128128
await run.stop();
129129
});
130130

131+
it('should not show warning when type files are used transitively', async () => {
132+
if (veEnabled) {
133+
// TODO: https://github.com/angular/angular-cli/issues/15056
134+
pending('Only supported in Ivy.');
135+
136+
return;
137+
}
138+
139+
host.writeMultipleFiles({
140+
'src/app/type.ts':
141+
`import {Myinterface} from './interface'; export type MyType = Myinterface;`,
142+
'src/app/interface.ts': 'export interface Myinterface {nbr: number;}',
143+
});
144+
145+
host.replaceInFile(
146+
'src/app/app.component.ts',
147+
`'@angular/core';`,
148+
`'@angular/core';\nimport { MyType } from './type';\n`,
149+
);
150+
151+
const logger = new TestLogger('unused-files-warnings');
152+
const run = await architect.scheduleTarget(targetSpec, undefined, { logger });
153+
const output = await run.result as BrowserBuilderOutput;
154+
expect(output.success).toBe(true);
155+
expect(logger.includes(warningMessageSuffix)).toBe(false);
156+
logger.clear();
157+
158+
await run.stop();
159+
});
160+
131161
it('works for rebuilds', async () => {
132162
if (veEnabled) {
133163
// TODO: https://github.com/angular/angular-cli/issues/15056

packages/ngtools/webpack/src/angular_compiler_plugin.ts

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -613,31 +613,26 @@ export class AngularCompilerPlugin {
613613
// - __ng_typecheck__.ts will never be requested.
614614
const fileExcludeRegExp = /(\.(d|ngfactory|ngstyle|ngsummary)\.ts|ng_typecheck__\.ts)$/;
615615

616-
const usedFiles = new Set<string>();
617-
for (const compilationModule of compilation.modules) {
618-
if (!compilationModule.resource) {
619-
continue;
616+
// Start with a set of all the source file names we care about.
617+
const unusedSourceFileNames = new Set(
618+
program.getSourceFiles()
619+
.map(x => this._compilerHost.denormalizePath(x.fileName))
620+
.filter(f => !(fileExcludeRegExp.test(f) || this._unusedFiles.has(f))),
621+
);
622+
// This function removes a source file name and all its dependencies from the set.
623+
const removeSourceFile = (fileName: string) => {
624+
if (unusedSourceFileNames.has(fileName)) {
625+
unusedSourceFileNames.delete(fileName);
626+
this.getDependencies(fileName, false).forEach(f => removeSourceFile(f));
620627
}
628+
};
621629

622-
usedFiles.add(forwardSlashPath(compilationModule.resource));
623-
624-
// We need the below for dependencies which
625-
// are not emitted such as type only TS files
626-
for (const dependency of compilationModule.buildInfo.fileDependencies) {
627-
usedFiles.add(forwardSlashPath(dependency));
628-
}
629-
}
630-
631-
const sourceFiles = program.getSourceFiles();
632-
for (const { fileName } of sourceFiles) {
633-
if (
634-
fileExcludeRegExp.test(fileName)
635-
|| usedFiles.has(fileName)
636-
|| this._unusedFiles.has(fileName)
637-
) {
638-
continue;
639-
}
630+
// Go over all the modules in the webpack compilation and remove them from the set.
631+
compilation.modules.forEach(m => m.resource ? removeSourceFile(m.resource) : null);
640632

633+
// Anything that remains is unused, because it wasn't referenced directly or transitively
634+
// on the files in the compilation.
635+
for (const fileName of unusedSourceFileNames) {
641636
compilation.warnings.push(
642637
`${fileName} is part of the TypeScript compilation but it's unused.\n` +
643638
`Add only entry points to the 'files' or 'include' properties in your tsconfig.`,
@@ -1210,7 +1205,7 @@ export class AngularCompilerPlugin {
12101205
return { outputText, sourceMap, errorDependencies };
12111206
}
12121207

1213-
getDependencies(fileName: string): string[] {
1208+
getDependencies(fileName: string, includeResources = true): string[] {
12141209
const resolvedFileName = this._compilerHost.resolve(fileName);
12151210
const sourceFile = this._compilerHost.getSourceFile(resolvedFileName, ts.ScriptTarget.Latest);
12161211
if (!sourceFile) {
@@ -1244,14 +1239,19 @@ export class AngularCompilerPlugin {
12441239
})
12451240
.filter(x => x) as string[];
12461241

1247-
const resourceImports = findResources(sourceFile)
1248-
.map(resourcePath => resolve(dirname(resolvedFileName), normalize(resourcePath)));
1242+
let resourceImports: string[] = [], resourceDependencies: string[] = [];
1243+
if (includeResources) {
1244+
resourceImports = findResources(sourceFile)
1245+
.map(resourcePath => resolve(dirname(resolvedFileName), normalize(resourcePath)));
1246+
resourceDependencies =
1247+
this.getResourceDependencies(this._compilerHost.denormalizePath(resolvedFileName));
1248+
}
12491249

12501250
// These paths are meant to be used by the loader so we must denormalize them.
12511251
const uniqueDependencies = new Set([
12521252
...esImports,
12531253
...resourceImports,
1254-
...this.getResourceDependencies(this._compilerHost.denormalizePath(resolvedFileName)),
1254+
...resourceDependencies,
12551255
].map((p) => p && this._compilerHost.denormalizePath(p)));
12561256

12571257
return [...uniqueDependencies];

0 commit comments

Comments
 (0)