feat(ivy): translate type-check diagnostics to their original source (#30181)
PR Close #30181
This commit is contained in:
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {CustomTransformers, Program} from '@angular/compiler-cli';
|
||||
import * as api from '@angular/compiler-cli/src/transformers/api';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {createCompilerHost, createProgram} from '../../ngtools2';
|
||||
@ -185,9 +186,8 @@ export class NgtscTestEnvironment {
|
||||
/**
|
||||
* Run the compiler to completion, and return any `ts.Diagnostic` errors that may have occurred.
|
||||
*/
|
||||
driveDiagnostics(): ReadonlyArray<ts.Diagnostic> {
|
||||
// Cast is safe as ngtsc mode only produces ts.Diagnostics.
|
||||
return mainDiagnosticsForTest(['-p', this.basePath]) as ReadonlyArray<ts.Diagnostic>;
|
||||
driveDiagnostics(): ReadonlyArray<ts.Diagnostic|api.Diagnostic> {
|
||||
return mainDiagnosticsForTest(['-p', this.basePath]);
|
||||
}
|
||||
|
||||
driveRoutes(entryPoint?: string): LazyRoute[] {
|
||||
|
@ -2979,7 +2979,8 @@ runInEachFileSystem(os => {
|
||||
'entrypoint.');
|
||||
|
||||
// Verify that the error is for the correct class.
|
||||
const id = expectTokenAtPosition(errors[0].file !, errors[0].start !, ts.isIdentifier);
|
||||
const error = errors[0] as ts.Diagnostic;
|
||||
const id = expectTokenAtPosition(error.file !, error.start !, ts.isIdentifier);
|
||||
expect(id.text).toBe('Dir');
|
||||
expect(ts.isClassDeclaration(id.parent)).toBe(true);
|
||||
});
|
||||
|
@ -5,10 +5,13 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Diagnostic} from '@angular/compiler-cli';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, ngErrorCode} from '../../src/ngtsc/diagnostics';
|
||||
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
|
||||
import {getTokenAtPosition} from '../../src/ngtsc/util/src/typescript';
|
||||
import {loadStandardTestFiles} from '../helpers/src/mock_file_loading';
|
||||
|
||||
import {NgtscTestEnvironment} from './env';
|
||||
@ -179,11 +182,12 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
|
||||
function diagnosticToNode<T extends ts.Node>(
|
||||
diag: ts.Diagnostic, guard: (node: ts.Node) => node is T): T {
|
||||
diagnostic: ts.Diagnostic | Diagnostic, guard: (node: ts.Node) => node is T): T {
|
||||
const diag = diagnostic as ts.Diagnostic;
|
||||
if (diag.file === undefined) {
|
||||
throw new Error(`Expected ts.Diagnostic to have a file source`);
|
||||
}
|
||||
const node = (ts as any).getTokenAtPosition(diag.file, diag.start) as ts.Node;
|
||||
const node = getTokenAtPosition(diag.file, diag.start !);
|
||||
expect(guard(node)).toBe(true);
|
||||
return node as T;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Diagnostic} from '@angular/compiler-cli';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
|
||||
@ -171,6 +172,7 @@ export declare class CommonModule {
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText).toContain('does_not_exist');
|
||||
expect(formatSpan(diags[0])).toEqual('/test.ts: 6:51, 6:70');
|
||||
});
|
||||
|
||||
it('should accept an NgFor iteration over an any-typed value', () => {
|
||||
@ -271,6 +273,7 @@ export declare class CommonModule {
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText).toContain('does_not_exist');
|
||||
expect(formatSpan(diags[0])).toEqual('/test.ts: 6:51, 6:70');
|
||||
});
|
||||
|
||||
it('should property type-check a microsyntax variable with the same name as the expression',
|
||||
@ -334,10 +337,48 @@ export declare class CommonModule {
|
||||
// Error from the binding to [fromBase].
|
||||
expect(diags[0].messageText)
|
||||
.toBe(`Type 'number' is not assignable to type 'string | undefined'.`);
|
||||
expect(formatSpan(diags[0])).toEqual('/test.ts: 19:28, 19:42');
|
||||
|
||||
// Error from the binding to [fromChild].
|
||||
expect(diags[1].messageText)
|
||||
.toBe(`Type 'number' is not assignable to type 'boolean | undefined'.`);
|
||||
expect(formatSpan(diags[1])).toEqual('/test.ts: 19:43, 19:58');
|
||||
});
|
||||
|
||||
it('should report diagnostics for external template files', () => {
|
||||
env.write('test.ts', `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'test',
|
||||
templateUrl: './template.html',
|
||||
})
|
||||
export class TestCmp {
|
||||
user: {name: string}[];
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [TestCmp],
|
||||
})
|
||||
export class Module {}
|
||||
`);
|
||||
env.write('template.html', `<div>
|
||||
<span>{{user.does_not_exist}}</span>
|
||||
</div>`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText).toContain('does_not_exist');
|
||||
expect(formatSpan(diags[0])).toEqual('/template.html: 1:14, 1:33');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function formatSpan(diagnostic: ts.Diagnostic | Diagnostic): string {
|
||||
if (diagnostic.source !== 'angular') {
|
||||
return '<unexpected non-angular span>';
|
||||
}
|
||||
const span = (diagnostic as Diagnostic).span !;
|
||||
const fileName = span.start.file.url.replace(/^C:\//, '/');
|
||||
return `${fileName}: ${span.start.line}:${span.start.col}, ${span.end.line}:${span.end.col}`;
|
||||
}
|
||||
|
Reference in New Issue
Block a user