Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(@angular-devkit/build-angular): allow internal Angular compilation control of diagnostic modes #26861

Merged
merged 1 commit into from Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -20,6 +20,14 @@ export interface EmitFileResult {
dependencies?: readonly string[];
}

export enum DiagnosticModes {
None = 0,
Option = 1 << 0,
Syntactic = 1 << 1,
Semantic = 1 << 2,
All = Option | Syntactic | Semantic,
}

export abstract class AngularCompilation {
static #angularCompilerCliModule?: typeof ng;
static #typescriptModule?: typeof ts;
Expand Down Expand Up @@ -71,19 +79,21 @@ export abstract class AngularCompilation {

abstract emitAffectedFiles(): Iterable<EmitFileResult> | Promise<Iterable<EmitFileResult>>;

protected abstract collectDiagnostics():
| Iterable<ts.Diagnostic>
| Promise<Iterable<ts.Diagnostic>>;
protected abstract collectDiagnostics(
modes: DiagnosticModes,
): Iterable<ts.Diagnostic> | Promise<Iterable<ts.Diagnostic>>;

async diagnoseFiles(): Promise<{ errors?: PartialMessage[]; warnings?: PartialMessage[] }> {
async diagnoseFiles(
modes = DiagnosticModes.All,
): Promise<{ errors?: PartialMessage[]; warnings?: PartialMessage[] }> {
const result: { errors?: PartialMessage[]; warnings?: PartialMessage[] } = {};

// Avoid loading typescript until actually needed.
// This allows for avoiding the load of typescript in the main thread when using the parallel compilation.
const typescript = await AngularCompilation.loadTypescript();

await profileAsync('NG_DIAGNOSTICS_TOTAL', async () => {
for (const diagnostic of await this.collectDiagnostics()) {
for (const diagnostic of await this.collectDiagnostics(modes)) {
const message = convertTypeScriptDiagnostic(typescript, diagnostic);
if (diagnostic.category === typescript.DiagnosticCategory.Error) {
(result.errors ??= []).push(message);
Expand Down
Expand Up @@ -16,7 +16,7 @@ import {
ensureSourceFileVersions,
} from '../angular-host';
import { createWorkerTransformer } from '../web-worker-transformer';
import { AngularCompilation, EmitFileResult } from './angular-compilation';
import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-compilation';

// Temporary deep import for transformer support
// TODO: Move these to a private exports location or move the implementation into this package.
Expand Down Expand Up @@ -127,7 +127,7 @@ export class AotCompilation extends AngularCompilation {
return { affectedFiles, compilerOptions, referencedFiles };
}

*collectDiagnostics(): Iterable<ts.Diagnostic> {
*collectDiagnostics(modes: DiagnosticModes): Iterable<ts.Diagnostic> {
assert(this.#state, 'Angular compilation must be initialized prior to collecting diagnostics.');
const {
affectedFiles,
Expand All @@ -137,25 +137,39 @@ export class AotCompilation extends AngularCompilation {
typeScriptProgram,
} = this.#state;

const syntactic = modes & DiagnosticModes.Syntactic;
const semantic = modes & DiagnosticModes.Semantic;

// Collect program level diagnostics
yield* typeScriptProgram.getConfigFileParsingDiagnostics();
yield* angularCompiler.getOptionDiagnostics();
yield* typeScriptProgram.getOptionsDiagnostics();
yield* typeScriptProgram.getGlobalDiagnostics();
if (modes & DiagnosticModes.Option) {
yield* typeScriptProgram.getConfigFileParsingDiagnostics();
yield* angularCompiler.getOptionDiagnostics();
yield* typeScriptProgram.getOptionsDiagnostics();
}
if (syntactic) {
yield* typeScriptProgram.getGlobalDiagnostics();
}

// Collect source file specific diagnostics
for (const sourceFile of typeScriptProgram.getSourceFiles()) {
if (angularCompiler.ignoreForDiagnostics.has(sourceFile)) {
continue;
}

// TypeScript will use cached diagnostics for files that have not been
// changed or affected for this build when using incremental building.
yield* profileSync(
'NG_DIAGNOSTICS_SYNTACTIC',
() => typeScriptProgram.getSyntacticDiagnostics(sourceFile),
true,
);
if (syntactic) {
// TypeScript will use cached diagnostics for files that have not been
// changed or affected for this build when using incremental building.
yield* profileSync(
'NG_DIAGNOSTICS_SYNTACTIC',
() => typeScriptProgram.getSyntacticDiagnostics(sourceFile),
true,
);
}

if (!semantic) {
continue;
}

yield* profileSync(
'NG_DIAGNOSTICS_SEMANTIC',
() => typeScriptProgram.getSemanticDiagnostics(sourceFile),
Expand Down
Expand Up @@ -6,6 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/

export { AngularCompilation } from './angular-compilation';
export { AngularCompilation, DiagnosticModes } from './angular-compilation';
export { createAngularCompilation } from './factory';
export { NoopCompilation } from './noop-compilation';
Expand Up @@ -13,7 +13,7 @@ import { profileSync } from '../../profiling';
import { AngularHostOptions, createAngularCompilerHost } from '../angular-host';
import { createJitResourceTransformer } from '../jit-resource-transformer';
import { createWorkerTransformer } from '../web-worker-transformer';
import { AngularCompilation, EmitFileResult } from './angular-compilation';
import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-compilation';

class JitCompilationState {
constructor(
Expand Down Expand Up @@ -82,18 +82,26 @@ export class JitCompilation extends AngularCompilation {
return { affectedFiles, compilerOptions, referencedFiles };
}

*collectDiagnostics(): Iterable<ts.Diagnostic> {
*collectDiagnostics(modes: DiagnosticModes): Iterable<ts.Diagnostic> {
assert(this.#state, 'Compilation must be initialized prior to collecting diagnostics.');
const { typeScriptProgram } = this.#state;

// Collect program level diagnostics
yield* typeScriptProgram.getConfigFileParsingDiagnostics();
yield* typeScriptProgram.getOptionsDiagnostics();
yield* typeScriptProgram.getGlobalDiagnostics();
yield* profileSync('NG_DIAGNOSTICS_SYNTACTIC', () =>
typeScriptProgram.getSyntacticDiagnostics(),
);
yield* profileSync('NG_DIAGNOSTICS_SEMANTIC', () => typeScriptProgram.getSemanticDiagnostics());
if (modes & DiagnosticModes.Option) {
yield* typeScriptProgram.getConfigFileParsingDiagnostics();
yield* typeScriptProgram.getOptionsDiagnostics();
}
if (modes & DiagnosticModes.Syntactic) {
yield* typeScriptProgram.getGlobalDiagnostics();
yield* profileSync('NG_DIAGNOSTICS_SYNTACTIC', () =>
typeScriptProgram.getSyntacticDiagnostics(),
);
}
if (modes & DiagnosticModes.Semantic) {
yield* profileSync('NG_DIAGNOSTICS_SEMANTIC', () =>
typeScriptProgram.getSemanticDiagnostics(),
);
}
}

emitAffectedFiles(): Iterable<EmitFileResult> {
Expand Down
Expand Up @@ -13,7 +13,7 @@ import { MessageChannel } from 'node:worker_threads';
import Piscina from 'piscina';
import type { SourceFile } from 'typescript';
import type { AngularHostOptions } from '../angular-host';
import { AngularCompilation, EmitFileResult } from './angular-compilation';
import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-compilation';

/**
* An Angular compilation which uses a Node.js Worker thread to load and execute
Expand Down Expand Up @@ -122,8 +122,10 @@ export class ParallelCompilation extends AngularCompilation {
throw new Error('Not implemented in ParallelCompilation.');
}

override diagnoseFiles(): Promise<{ errors?: PartialMessage[]; warnings?: PartialMessage[] }> {
return this.#worker.run(undefined, { name: 'diagnose' });
override diagnoseFiles(
modes = DiagnosticModes.All,
): Promise<{ errors?: PartialMessage[]; warnings?: PartialMessage[] }> {
return this.#worker.run(modes, { name: 'diagnose' });
}

override emitAffectedFiles(): Promise<Iterable<EmitFileResult>> {
Expand Down
Expand Up @@ -11,7 +11,7 @@ import assert from 'node:assert';
import { randomUUID } from 'node:crypto';
import { type MessagePort, receiveMessageOnPort } from 'node:worker_threads';
import { SourceFileCache } from '../source-file-cache';
import type { AngularCompilation } from './angular-compilation';
import type { AngularCompilation, DiagnosticModes } from './angular-compilation';
import { AotCompilation } from './aot-compilation';
import { JitCompilation } from './jit-compilation';

Expand Down Expand Up @@ -99,13 +99,13 @@ export async function initialize(request: InitRequest) {
};
}

export async function diagnose(): Promise<{
export async function diagnose(modes: DiagnosticModes): Promise<{
errors?: PartialMessage[];
warnings?: PartialMessage[];
}> {
assert(compilation);

const diagnostics = await compilation.diagnoseFiles();
const diagnostics = await compilation.diagnoseFiles(modes);

return diagnostics;
}
Expand Down
Expand Up @@ -17,13 +17,18 @@ import type {
} from 'esbuild';
import assert from 'node:assert';
import * as path from 'node:path';
import { maxWorkers } from '../../../utils/environment-options';
import { maxWorkers, useTypeChecking } from '../../../utils/environment-options';
import { JavaScriptTransformer } from '../javascript-transformer';
import { LoadResultCache, createCachedLoad } from '../load-result-cache';
import { logCumulativeDurations, profileAsync, resetCumulativeDurations } from '../profiling';
import { BundleStylesheetOptions } from '../stylesheets/bundle-options';
import { AngularHostOptions } from './angular-host';
import { AngularCompilation, NoopCompilation, createAngularCompilation } from './compilation';
import {
AngularCompilation,
DiagnosticModes,
NoopCompilation,
createAngularCompilation,
} from './compilation';
import { SharedTSCompilationState, getSharedCompilationState } from './compilation-state';
import { ComponentStylesheetBundler } from './component-stylesheets';
import { FileReferenceTracker } from './file-reference-tracker';
Expand Down Expand Up @@ -258,14 +263,6 @@ export function createCompilerPlugin(
return result;
}

const diagnostics = await compilation.diagnoseFiles();
if (diagnostics.errors?.length) {
(result.errors ??= []).push(...diagnostics.errors);
}
if (diagnostics.warnings?.length) {
(result.warnings ??= []).push(...diagnostics.warnings);
}

// Update TypeScript file output cache for all affected files
try {
await profileAsync('NG_EMIT_TS', async () => {
Expand All @@ -286,6 +283,16 @@ export function createCompilerPlugin(
});
}

const diagnostics = await compilation.diagnoseFiles(
useTypeChecking ? DiagnosticModes.All : DiagnosticModes.All & ~DiagnosticModes.Semantic,
);
if (diagnostics.errors?.length) {
(result.errors ??= []).push(...diagnostics.errors);
}
if (diagnostics.warnings?.length) {
(result.warnings ??= []).push(...diagnostics.warnings);
}

// Add errors from failed additional results.
// This must be done after emit to capture latest web worker results.
for (const { errors } of additionalResults.values()) {
Expand Down
Expand Up @@ -102,3 +102,7 @@ export const debugPerformance = isPresent(debugPerfVariable) && isEnabled(debugP

const watchRootVariable = process.env['NG_BUILD_WATCH_ROOT'];
export const shouldWatchRoot = isPresent(watchRootVariable) && isEnabled(watchRootVariable);

const typeCheckingVariable = process.env['NG_BUILD_TYPE_CHECK'];
export const useTypeChecking =
!isPresent(typeCheckingVariable) || !isDisabled(typeCheckingVariable);