feat(ivy): translate type-check diagnostics to their original source (#30181)

PR Close #30181
This commit is contained in:
JoostK
2019-04-28 00:26:13 +02:00
committed by Miško Hevery
parent 489cef6ea2
commit 3a2b195a58
17 changed files with 823 additions and 90 deletions

View File

@ -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[] {

View File

@ -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);
});

View File

@ -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;
}

View File

@ -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}`;
}