Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -278,5 +278,45 @@ describeLibraryBuilder(executeLibraryBuilder, LIBRARY_BUILDER_INFO, (harness) =>
expect(dtsContent).toContain('LibComponent');
expect(dtsContent).toContain('CACHED_BUILD');
});

it('should report TypeScript declaration emit diagnostics', async () => {
await harness.writeFiles({
'projects/lib/src/lib/internal.ts': `
interface PrivateData {
value: number;
}

export const internalData: PrivateData = { value: 42 };
`,
'projects/lib/src/lib/feature.ts': `
import { internalData } from './internal';

export const data = internalData;
`,
'projects/lib/src/public-api.ts': `
export * from './lib/feature';
export * from './lib/lib.service';
export * from './lib/lib.component';
`,
});

harness.useTarget('build', {
...BASE_OPTIONS,
});

const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false });
expect(result?.success).toBeFalse();
expect(logs).toContain(
jasmine.objectContaining({
level: 'error',
message: jasmine.stringMatching(/TS4023:.*PrivateData/),
}),
);
expect(logs).not.toContain(
jasmine.objectContaining({
message: jasmine.stringContaining('UNRESOLVED_IMPORT'),
}),
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -905,5 +905,84 @@ describeLibraryBuilder(executeLibraryBuilder, LIBRARY_BUILDER_INFO, (harness) =>
},
]);
});

it('should preserve and recover from TypeScript declaration emit diagnostics in watch mode', async () => {
await harness.writeFiles({
'projects/lib/src/lib/internal.ts': `
interface PrivateData {
value: number;
}
export const internalData: PrivateData = { value: 42 };
`,
'projects/lib/src/lib/feature.ts': `
import { internalData } from './internal';
export const data = internalData;
`,
'projects/lib/src/public-api.ts': `
export * from './lib/feature';
export * from './lib/lib.service';
export * from './lib/lib.component';
`,
});

harness.useTarget('build', {
...BASE_OPTIONS,
watch: true,
});

await harness.executeWithCases(
[
async ({ result, logs }) => {
expect(result?.success).toBeFalse();
expect(logs).toContain(
jasmine.objectContaining({
level: 'error',
message: jasmine.stringMatching(/TS4023:.*PrivateData/),
}),
);

// Modify an unrelated file so feature.ts is not re-emitted
await harness.writeFile(
'projects/lib/src/lib/lib.service.ts',
`
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class LibService {
updated = true;
}
`,
);
},
async ({ result, logs }) => {
expect(result?.success).toBeFalse();
expect(logs).toContain(
jasmine.objectContaining({
level: 'error',
message: jasmine.stringMatching(/TS4023:.*PrivateData/),
}),
);

// Fix the declaration error in feature.ts by adding an explicit type annotation
await harness.writeFile(
'projects/lib/src/lib/feature.ts',
`
import { internalData } from './internal';
export const data: { value: number } = internalData;
`,
);
},
({ result }) => {
expect(result?.success).toBeTrue();
const dtsContent = harness.readFile('dist/lib/types/lib.d.ts');
expect(dtsContent).toContain('data');
},
],
{ outputLogsOnFailure: false },
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class AngularCompilationState {
public readonly webWorkerTransform: ts.TransformerFactory<ts.SourceFile>,
public readonly useTypeScriptTranspilation: boolean,
public readonly diagnosticCache = new WeakMap<ts.SourceFile, ts.Diagnostic[]>(),
public readonly declarationDiagnostics = new WeakMap<ts.SourceFile, readonly ts.Diagnostic[]>(),
public readonly filesWithoutDtsSignature = new Set<string>(),
public readonly useFallbackRootNames = false,
) {}
Expand Down Expand Up @@ -358,6 +359,7 @@ export class AotCompilation extends TypeScriptCompilation {
createWorkerTransformer(hostOptions.processWebWorker.bind(hostOptions)),
useTypeScriptTranspilation,
this.#state?.diagnosticCache,
this.#state?.declarationDiagnostics,
filesWithoutDtsSignature,
useFallbackRootNames,
);
Expand All @@ -377,6 +379,7 @@ export class AotCompilation extends TypeScriptCompilation {
const {
affectedFiles,
angularCompiler,
declarationDiagnostics,
diagnosticCache,
templateDiagnosticsOptimization,
typeScriptProgram,
Expand Down Expand Up @@ -422,11 +425,16 @@ export class AotCompilation extends TypeScriptCompilation {
true,
);

// Declaration files cannot have template diagnostics
// Declaration files cannot have template or declaration emit diagnostics
if (sourceFile.isDeclarationFile) {
continue;
}

const fileDeclarationDiagnostics = declarationDiagnostics.get(sourceFile);
if (fileDeclarationDiagnostics) {
yield* fileDeclarationDiagnostics;
}

// Only request Angular template diagnostics for affected files to avoid
// overhead of template diagnostics for unchanged files.
if (affectedFiles.has(sourceFile)) {
Expand Down Expand Up @@ -493,6 +501,7 @@ export class AotCompilation extends TypeScriptCompilation {
affectedFiles,
angularCompiler,
compilerHost,
declarationDiagnostics,
typeScriptProgram,
webWorkerTransform,
useTypeScriptTranspilation,
Expand Down Expand Up @@ -548,15 +557,22 @@ export class AotCompilation extends TypeScriptCompilation {
// Emit is handled in write file callback when using TypeScript
if (useTypeScriptTranspilation) {
// TypeScript will loop until there are no more affected files in the program
while (
typeScriptProgram.emitNextAffectedFile(
while (true) {
const affectedEmitResult = typeScriptProgram.emitNextAffectedFile(
writeFileCallback,
undefined,
undefined,
transformers,
)
) {
/* empty */
);
if (!affectedEmitResult) {
break;
}
if ('kind' in affectedEmitResult.affected) {
declarationDiagnostics.set(
affectedEmitResult.affected,
affectedEmitResult.result.diagnostics,
);
}
}
} else if (compilerOptions.tsBuildInfoFile) {
// Manually get the builder state for the persistent cache
Expand Down Expand Up @@ -589,10 +605,18 @@ export class AotCompilation extends TypeScriptCompilation {
}

if (useTypeScriptTranspilation) {
const emitOnly = emittedDtsSourceFiles.has(sourceFile)
? (EmitOnly.Js as unknown as boolean)
: undefined;
typeScriptProgram.emit(sourceFile, writeFileCallback, undefined, emitOnly, transformers);
const hasEmittedDts = emittedDtsSourceFiles.has(sourceFile);
const emitOnly = hasEmittedDts ? (EmitOnly.Js as unknown as boolean) : undefined;
const emitResult = typeScriptProgram.emit(
sourceFile,
writeFileCallback,
undefined,
emitOnly,
transformers,
);
if (!hasEmittedDts) {
declarationDiagnostics.set(sourceFile, emitResult.diagnostics);
}
continue;
}

Expand Down
Loading