feat(compiler-cli): improve error messages produced during structural errors (#20459)
The errors produced when error were encountered while interpreting the content of a directive was often incomprehencible. With this change these kind of error messages should be easier to understand and diagnose. PR Close #20459
This commit is contained in:

committed by
Miško Hevery

parent
1366762d12
commit
8ecda94899
@ -20,7 +20,6 @@ import {GENERATED_FILES} from './transformers/util';
|
||||
|
||||
import {exitCodeFromResult, performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration, PerformCompilationResult, filterErrorsAndWarnings} from './perform_compile';
|
||||
import {performWatchCompilation, createPerformWatchHost} from './perform_watch';
|
||||
import {isSyntaxError} from '@angular/compiler';
|
||||
|
||||
export function main(
|
||||
args: string[], consoleError: (s: string) => void = console.error,
|
||||
|
@ -8,8 +8,8 @@
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Evaluator, errorSymbol} from './evaluator';
|
||||
import {ClassMetadata, ConstructorMetadata, FunctionMetadata, InterfaceMetadata, METADATA_VERSION, MemberMetadata, MetadataEntry, MetadataError, MetadataMap, MetadataSymbolicBinaryExpression, MetadataSymbolicCallExpression, MetadataSymbolicExpression, MetadataSymbolicIfExpression, MetadataSymbolicIndexExpression, MetadataSymbolicPrefixExpression, MetadataSymbolicReferenceExpression, MetadataSymbolicSelectExpression, MetadataSymbolicSpreadExpression, MetadataValue, MethodMetadata, ModuleExportMetadata, ModuleMetadata, isClassMetadata, isConstructorMetadata, isFunctionMetadata, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataSymbolicExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSelectExpression, isMethodMetadata} from './schema';
|
||||
import {Evaluator, errorSymbol, recordMapEntry} from './evaluator';
|
||||
import {ClassMetadata, ConstructorMetadata, FunctionMetadata, InterfaceMetadata, METADATA_VERSION, MemberMetadata, MetadataEntry, MetadataError, MetadataMap, MetadataSymbolicBinaryExpression, MetadataSymbolicCallExpression, MetadataSymbolicExpression, MetadataSymbolicIfExpression, MetadataSymbolicIndexExpression, MetadataSymbolicPrefixExpression, MetadataSymbolicReferenceExpression, MetadataSymbolicSelectExpression, MetadataSymbolicSpreadExpression, MetadataValue, MethodMetadata, ModuleExportMetadata, ModuleMetadata, isClassMetadata, isConstructorMetadata, isFunctionMetadata, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportDefaultReference, isMetadataImportedSymbolReferenceExpression, isMetadataSymbolicExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSelectExpression, isMethodMetadata} from './schema';
|
||||
import {Symbols} from './symbols';
|
||||
|
||||
const isStatic = (node: ts.Node) => ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Static;
|
||||
@ -76,8 +76,7 @@ export class MetadataCollector {
|
||||
}
|
||||
|
||||
function recordEntry<T extends MetadataEntry>(entry: T, node: ts.Node): T {
|
||||
nodeMap.set(entry, node);
|
||||
return entry;
|
||||
return recordMapEntry(entry, node, nodeMap, sourceFile);
|
||||
}
|
||||
|
||||
function errorSym(
|
||||
|
@ -9,10 +9,11 @@
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CollectorOptions} from './collector';
|
||||
import {MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataSymbolicCallExpression, MetadataValue, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSpreadExpression} from './schema';
|
||||
import {ClassMetadata, FunctionMetadata, InterfaceMetadata, MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataSourceLocationInfo, MetadataSymbolicCallExpression, MetadataValue, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportDefaultReference, isMetadataImportedSymbolReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSpreadExpression} from './schema';
|
||||
import {Symbols} from './symbols';
|
||||
|
||||
|
||||
|
||||
// In TypeScript 2.1 the spread element kind was renamed.
|
||||
const spreadElementSyntaxKind: ts.SyntaxKind =
|
||||
(ts.SyntaxKind as any).SpreadElement || (ts.SyntaxKind as any).SpreadElementExpression;
|
||||
@ -38,6 +39,24 @@ function isCallOf(callExpression: ts.CallExpression, ident: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function recordMapEntry<T extends MetadataEntry>(
|
||||
entry: T, node: ts.Node,
|
||||
nodeMap: Map<MetadataValue|ClassMetadata|InterfaceMetadata|FunctionMetadata, ts.Node>,
|
||||
sourceFile?: ts.SourceFile) {
|
||||
if (!nodeMap.has(entry)) {
|
||||
nodeMap.set(entry, node);
|
||||
if (node && (isMetadataImportedSymbolReferenceExpression(entry) ||
|
||||
isMetadataImportDefaultReference(entry)) &&
|
||||
entry.line == null) {
|
||||
const info = sourceInfo(node, sourceFile);
|
||||
if (info.line != null) entry.line = info.line;
|
||||
if (info.character != null) entry.character = info.character;
|
||||
}
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* ts.forEachChild stops iterating children when the callback return a truthy value.
|
||||
* This method inverts this to implement an `every` style iterator. It will return
|
||||
@ -77,21 +96,22 @@ function getSourceFileOfNode(node: ts.Node | undefined): ts.SourceFile {
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function errorSymbol(
|
||||
message: string, node?: ts.Node, context?: {[name: string]: string},
|
||||
sourceFile?: ts.SourceFile): MetadataError {
|
||||
let result: MetadataError|undefined = undefined;
|
||||
export function sourceInfo(
|
||||
node: ts.Node | undefined, sourceFile: ts.SourceFile | undefined): MetadataSourceLocationInfo {
|
||||
if (node) {
|
||||
sourceFile = sourceFile || getSourceFileOfNode(node);
|
||||
if (sourceFile) {
|
||||
const {line, character} =
|
||||
ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile));
|
||||
result = {__symbolic: 'error', message, line, character};
|
||||
return ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile));
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
result = {__symbolic: 'error', message};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function errorSymbol(
|
||||
message: string, node?: ts.Node, context?: {[name: string]: string},
|
||||
sourceFile?: ts.SourceFile): MetadataError {
|
||||
const result: MetadataError = {__symbolic: 'error', message, ...sourceInfo(node, sourceFile)};
|
||||
if (context) {
|
||||
result.context = context;
|
||||
}
|
||||
@ -242,8 +262,7 @@ export class Evaluator {
|
||||
}
|
||||
entry = newEntry;
|
||||
}
|
||||
t.nodeMap.set(entry, node);
|
||||
return entry;
|
||||
return recordMapEntry(entry, node, t.nodeMap);
|
||||
}
|
||||
|
||||
function isFoldableError(value: any): value is MetadataError {
|
||||
@ -256,6 +275,9 @@ export class Evaluator {
|
||||
// Encode as a global reference. StaticReflector will check the reference.
|
||||
return recordEntry({__symbolic: 'reference', name}, node);
|
||||
}
|
||||
if (reference && isMetadataSymbolicReferenceExpression(reference)) {
|
||||
return recordEntry({...reference}, node);
|
||||
}
|
||||
return reference;
|
||||
};
|
||||
|
||||
@ -628,7 +650,7 @@ export class Evaluator {
|
||||
return recordEntry({__symbolic: 'if', condition, thenExpression, elseExpression}, node);
|
||||
case ts.SyntaxKind.FunctionExpression:
|
||||
case ts.SyntaxKind.ArrowFunction:
|
||||
return recordEntry(errorSymbol('Function call not supported', node), node);
|
||||
return recordEntry(errorSymbol('Lambda not supported', node), node);
|
||||
case ts.SyntaxKind.TaggedTemplateExpression:
|
||||
return recordEntry(
|
||||
errorSymbol('Tagged template expressions are not supported in metadata', node), node);
|
||||
|
@ -178,7 +178,20 @@ export function isMetadataSymbolicIfExpression(value: any): value is MetadataSym
|
||||
return value && value.__symbolic === 'if';
|
||||
}
|
||||
|
||||
export interface MetadataGlobalReferenceExpression extends MetadataSymbolicExpression {
|
||||
export interface MetadataSourceLocationInfo {
|
||||
/**
|
||||
* The line number of the error in the .ts file the metadata was created for.
|
||||
*/
|
||||
line?: number;
|
||||
|
||||
/**
|
||||
* The number of utf8 code-units from the beginning of the file of the error.
|
||||
*/
|
||||
character?: number;
|
||||
}
|
||||
|
||||
export interface MetadataGlobalReferenceExpression extends MetadataSymbolicExpression,
|
||||
MetadataSourceLocationInfo {
|
||||
__symbolic: 'reference';
|
||||
name: string;
|
||||
arguments?: MetadataValue[];
|
||||
@ -188,7 +201,8 @@ export function isMetadataGlobalReferenceExpression(value: any):
|
||||
return value && value.name && !value.module && isMetadataSymbolicReferenceExpression(value);
|
||||
}
|
||||
|
||||
export interface MetadataModuleReferenceExpression extends MetadataSymbolicExpression {
|
||||
export interface MetadataModuleReferenceExpression extends MetadataSymbolicExpression,
|
||||
MetadataSourceLocationInfo {
|
||||
__symbolic: 'reference';
|
||||
module: string;
|
||||
}
|
||||
@ -198,7 +212,8 @@ export function isMetadataModuleReferenceExpression(value: any):
|
||||
isMetadataSymbolicReferenceExpression(value);
|
||||
}
|
||||
|
||||
export interface MetadataImportedSymbolReferenceExpression extends MetadataSymbolicExpression {
|
||||
export interface MetadataImportedSymbolReferenceExpression extends MetadataSymbolicExpression,
|
||||
MetadataSourceLocationInfo {
|
||||
__symbolic: 'reference';
|
||||
module: string;
|
||||
name: string;
|
||||
@ -209,7 +224,8 @@ export function isMetadataImportedSymbolReferenceExpression(value: any):
|
||||
return value && value.module && !!value.name && isMetadataSymbolicReferenceExpression(value);
|
||||
}
|
||||
|
||||
export interface MetadataImportedDefaultReferenceExpression extends MetadataSymbolicExpression {
|
||||
export interface MetadataImportedDefaultReferenceExpression extends MetadataSymbolicExpression,
|
||||
MetadataSourceLocationInfo {
|
||||
__symbolic: 'reference';
|
||||
module: string;
|
||||
default:
|
||||
@ -218,7 +234,7 @@ export interface MetadataImportedDefaultReferenceExpression extends MetadataSymb
|
||||
}
|
||||
export function isMetadataImportDefaultReference(value: any):
|
||||
value is MetadataImportedDefaultReferenceExpression {
|
||||
return value.module && value.default && isMetadataSymbolicReferenceExpression(value);
|
||||
return value && value.module && value.default && isMetadataSymbolicReferenceExpression(value);
|
||||
}
|
||||
|
||||
export type MetadataSymbolicReferenceExpression = MetadataGlobalReferenceExpression |
|
||||
@ -248,7 +264,7 @@ export function isMetadataSymbolicSpreadExpression(value: any):
|
||||
return value && value.__symbolic === 'spread';
|
||||
}
|
||||
|
||||
export interface MetadataError {
|
||||
export interface MetadataError extends MetadataSourceLocationInfo {
|
||||
__symbolic: 'error';
|
||||
|
||||
/**
|
||||
@ -259,16 +275,6 @@ export interface MetadataError {
|
||||
*/
|
||||
message: string;
|
||||
|
||||
/**
|
||||
* The line number of the error in the .ts file the metadata was created for.
|
||||
*/
|
||||
line?: number;
|
||||
|
||||
/**
|
||||
* The number of utf8 code-units from the beginning of the file of the error.
|
||||
*/
|
||||
character?: number;
|
||||
|
||||
/**
|
||||
* The module of the error (only used in bundled metadata)
|
||||
*/
|
||||
@ -280,6 +286,7 @@ export interface MetadataError {
|
||||
*/
|
||||
context?: {[name: string]: string};
|
||||
}
|
||||
|
||||
export function isMetadataError(value: any): value is MetadataError {
|
||||
return value && value.__symbolic === 'error';
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isSyntaxError, syntaxError} from '@angular/compiler';
|
||||
import {Position, isSyntaxError, syntaxError} from '@angular/compiler';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
@ -29,30 +29,76 @@ const defaultFormatHost: ts.FormatDiagnosticsHost = {
|
||||
getNewLine: () => ts.sys.newLine
|
||||
};
|
||||
|
||||
function displayFileName(fileName: string, host: ts.FormatDiagnosticsHost): string {
|
||||
return path.relative(host.getCurrentDirectory(), host.getCanonicalFileName(fileName));
|
||||
}
|
||||
|
||||
export function formatDiagnosticPosition(
|
||||
position: Position, host: ts.FormatDiagnosticsHost = defaultFormatHost): string {
|
||||
return `${displayFileName(position.fileName, host)}(${position.line + 1},${position.column+1})`;
|
||||
}
|
||||
|
||||
export function flattenDiagnosticMessageChain(
|
||||
chain: api.DiagnosticMessageChain, host: ts.FormatDiagnosticsHost = defaultFormatHost): string {
|
||||
let result = chain.messageText;
|
||||
let indent = 1;
|
||||
let current = chain.next;
|
||||
const newLine = host.getNewLine();
|
||||
while (current) {
|
||||
result += newLine;
|
||||
for (let i = 0; i < indent; i++) {
|
||||
result += ' ';
|
||||
}
|
||||
result += current.messageText;
|
||||
const position = current.position;
|
||||
if (position) {
|
||||
result += ` at ${formatDiagnosticPosition(position, host)}`;
|
||||
}
|
||||
current = current.next;
|
||||
indent++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function formatDiagnostic(
|
||||
diagnostic: api.Diagnostic, host: ts.FormatDiagnosticsHost = defaultFormatHost) {
|
||||
let result = '';
|
||||
const newLine = host.getNewLine();
|
||||
const span = diagnostic.span;
|
||||
if (span) {
|
||||
result += `${formatDiagnosticPosition({
|
||||
fileName: span.start.file.url,
|
||||
line: span.start.line,
|
||||
column: span.start.col
|
||||
}, host)}: `;
|
||||
} else if (diagnostic.position) {
|
||||
result += `${formatDiagnosticPosition(diagnostic.position, host)}: `;
|
||||
}
|
||||
if (diagnostic.span && diagnostic.span.details) {
|
||||
result += `: ${diagnostic.span.details}, ${diagnostic.messageText}${newLine}`;
|
||||
} else if (diagnostic.chain) {
|
||||
result += `${flattenDiagnosticMessageChain(diagnostic.chain, host)}.${newLine}`;
|
||||
} else {
|
||||
result += `: ${diagnostic.messageText}${newLine}`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function formatDiagnostics(
|
||||
diags: Diagnostics, tsFormatHost: ts.FormatDiagnosticsHost = defaultFormatHost): string {
|
||||
diags: Diagnostics, host: ts.FormatDiagnosticsHost = defaultFormatHost): string {
|
||||
if (diags && diags.length) {
|
||||
return diags
|
||||
.map(d => {
|
||||
if (api.isTsDiagnostic(d)) {
|
||||
return ts.formatDiagnostics([d], tsFormatHost);
|
||||
.map(diagnostic => {
|
||||
if (api.isTsDiagnostic(diagnostic)) {
|
||||
return ts.formatDiagnostics([diagnostic], host);
|
||||
} else {
|
||||
let res = ts.DiagnosticCategory[d.category];
|
||||
if (d.span) {
|
||||
res +=
|
||||
` at ${d.span.start.file.url}(${d.span.start.line + 1},${d.span.start.col + 1})`;
|
||||
}
|
||||
if (d.span && d.span.details) {
|
||||
res += `: ${d.span.details}, ${d.messageText}\n`;
|
||||
} else {
|
||||
res += `: ${d.messageText}\n`;
|
||||
}
|
||||
return res;
|
||||
return formatDiagnostic(diagnostic, host);
|
||||
}
|
||||
})
|
||||
.join('');
|
||||
} else
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export interface ParsedConfiguration {
|
||||
|
@ -6,16 +6,24 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {GeneratedFile, ParseSourceSpan} from '@angular/compiler';
|
||||
import {GeneratedFile, ParseSourceSpan, Position} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
export const DEFAULT_ERROR_CODE = 100;
|
||||
export const UNKNOWN_ERROR_CODE = 500;
|
||||
export const SOURCE = 'angular' as 'angular';
|
||||
|
||||
export interface DiagnosticMessageChain {
|
||||
messageText: string;
|
||||
position?: Position;
|
||||
next?: DiagnosticMessageChain;
|
||||
}
|
||||
|
||||
export interface Diagnostic {
|
||||
messageText: string;
|
||||
span?: ParseSourceSpan;
|
||||
position?: Position;
|
||||
chain?: DiagnosticMessageChain;
|
||||
category: ts.DiagnosticCategory;
|
||||
code: number;
|
||||
source: 'angular';
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, GeneratedFile, MessageBundle, NgAnalyzedFile, NgAnalyzedModules, ParseSourceSpan, Serializer, TypeScriptEmitter, Xliff, Xliff2, Xmb, core, createAotCompiler, getParseErrors, isSyntaxError} from '@angular/compiler';
|
||||
import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, FormattedMessageChain, GeneratedFile, MessageBundle, NgAnalyzedFile, NgAnalyzedModules, ParseSourceSpan, Position, Serializer, TypeScriptEmitter, Xliff, Xliff2, Xmb, core, createAotCompiler, getParseErrors, isFormattedError, isSyntaxError} from '@angular/compiler';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
@ -14,14 +14,13 @@ import * as ts from 'typescript';
|
||||
import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics';
|
||||
import {ModuleMetadata, createBundleIndexHost} from '../metadata/index';
|
||||
|
||||
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, LazyRoute, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
|
||||
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, DiagnosticMessageChain, EmitFlags, LazyRoute, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
|
||||
import {CodeGenerator, TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host';
|
||||
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
|
||||
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
|
||||
import {GENERATED_FILES, StructureIsReused, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused} from './util';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Maximum number of files that are emitable via calling ts.Program.emit
|
||||
* passing individual targetSourceFiles.
|
||||
@ -378,10 +377,12 @@ class AngularCompilerProgram implements Program {
|
||||
}
|
||||
|
||||
private get structuralDiagnostics(): Diagnostic[] {
|
||||
if (!this._structuralDiagnostics) {
|
||||
let diagnostics = this._structuralDiagnostics;
|
||||
if (!diagnostics) {
|
||||
this.initSync();
|
||||
diagnostics = (this._structuralDiagnostics = this._structuralDiagnostics || []);
|
||||
}
|
||||
return this._structuralDiagnostics !;
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
private get tsProgram(): ts.Program {
|
||||
@ -430,16 +431,9 @@ class AngularCompilerProgram implements Program {
|
||||
this.rootNames, this.options, this.host, this.metadataCache, codegen,
|
||||
this.oldProgramLibrarySummaries);
|
||||
const aotOptions = getAotCompilerOptions(this.options);
|
||||
this._structuralDiagnostics = [];
|
||||
const errorCollector =
|
||||
(this.options.collectAllErrors || this.options.fullTemplateTypeCheck) ? (err: any) => {
|
||||
this._structuralDiagnostics !.push({
|
||||
messageText: err.toString(),
|
||||
category: ts.DiagnosticCategory.Error,
|
||||
source: SOURCE,
|
||||
code: DEFAULT_ERROR_CODE
|
||||
});
|
||||
} : undefined;
|
||||
const errorCollector = (this.options.collectAllErrors || this.options.fullTemplateTypeCheck) ?
|
||||
(err: any) => this._addStructuralDiagnostics(err) :
|
||||
undefined;
|
||||
this._compiler = createAotCompiler(this._hostAdapter, aotOptions, errorCollector).compiler;
|
||||
}
|
||||
|
||||
@ -522,33 +516,26 @@ class AngularCompilerProgram implements Program {
|
||||
this._hostAdapter.isSourceFile = () => false;
|
||||
this._tsProgram = ts.createProgram(this.rootNames, this.options, this.hostAdapter);
|
||||
if (isSyntaxError(e)) {
|
||||
const parserErrors = getParseErrors(e);
|
||||
if (parserErrors && parserErrors.length) {
|
||||
this._structuralDiagnostics = [
|
||||
...(this._structuralDiagnostics || []),
|
||||
...parserErrors.map<Diagnostic>(e => ({
|
||||
messageText: e.contextualMessage(),
|
||||
category: ts.DiagnosticCategory.Error,
|
||||
span: e.span,
|
||||
source: SOURCE,
|
||||
code: DEFAULT_ERROR_CODE
|
||||
}))
|
||||
];
|
||||
} else {
|
||||
this._structuralDiagnostics = [
|
||||
...(this._structuralDiagnostics || []), {
|
||||
messageText: e.message,
|
||||
category: ts.DiagnosticCategory.Error,
|
||||
source: SOURCE,
|
||||
code: DEFAULT_ERROR_CODE
|
||||
}
|
||||
];
|
||||
}
|
||||
this._addStructuralDiagnostics(e);
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
private _addStructuralDiagnostics(error: Error) {
|
||||
const diagnostics = this._structuralDiagnostics || (this._structuralDiagnostics = []);
|
||||
if (isSyntaxError(error)) {
|
||||
diagnostics.push(...syntaxErrorToDiagnostics(error));
|
||||
} else {
|
||||
diagnostics.push({
|
||||
messageText: error.toString(),
|
||||
category: ts.DiagnosticCategory.Error,
|
||||
source: SOURCE,
|
||||
code: DEFAULT_ERROR_CODE
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this returns a ts.Diagnostic so that we
|
||||
// can return errors in a ts.EmitResult
|
||||
private generateFilesForEmit(emitFlags: EmitFlags):
|
||||
@ -843,3 +830,56 @@ function mergeEmitResults(emitResults: ts.EmitResult[]): ts.EmitResult {
|
||||
}
|
||||
return {diagnostics, emitSkipped, emittedFiles};
|
||||
}
|
||||
|
||||
function diagnosticSourceOfSpan(span: ParseSourceSpan): ts.SourceFile {
|
||||
// For diagnostics, TypeScript only uses the fileName and text properties.
|
||||
// The redundant '()' are here is to avoid having clang-format breaking the line incorrectly.
|
||||
return ({ fileName: span.start.file.url, text: span.start.file.content } as any);
|
||||
}
|
||||
|
||||
function diagnosticSourceOfFileName(fileName: string, program: ts.Program): ts.SourceFile {
|
||||
const sourceFile = program.getSourceFile(fileName);
|
||||
if (sourceFile) return sourceFile;
|
||||
|
||||
// If we are reporting diagnostics for a source file that is not in the project then we need
|
||||
// to fake a source file so the diagnostic formatting routines can emit the file name.
|
||||
// The redundant '()' are here is to avoid having clang-format breaking the line incorrectly.
|
||||
return ({ fileName, text: '' } as any);
|
||||
}
|
||||
|
||||
|
||||
function diagnosticChainFromFormattedDiagnosticChain(chain: FormattedMessageChain):
|
||||
DiagnosticMessageChain {
|
||||
return {
|
||||
messageText: chain.message,
|
||||
next: chain.next && diagnosticChainFromFormattedDiagnosticChain(chain.next),
|
||||
position: chain.position
|
||||
};
|
||||
}
|
||||
|
||||
function syntaxErrorToDiagnostics(error: Error): Diagnostic[] {
|
||||
const parserErrors = getParseErrors(error);
|
||||
if (parserErrors && parserErrors.length) {
|
||||
return parserErrors.map<Diagnostic>(e => ({
|
||||
messageText: e.contextualMessage(),
|
||||
file: diagnosticSourceOfSpan(e.span),
|
||||
start: e.span.start.offset,
|
||||
length: e.span.end.offset - e.span.start.offset,
|
||||
category: ts.DiagnosticCategory.Error,
|
||||
source: SOURCE,
|
||||
code: DEFAULT_ERROR_CODE
|
||||
}));
|
||||
} else {
|
||||
if (isFormattedError(error)) {
|
||||
return [{
|
||||
messageText: error.message,
|
||||
chain: error.chain && diagnosticChainFromFormattedDiagnosticChain(error.chain),
|
||||
category: ts.DiagnosticCategory.Error,
|
||||
source: SOURCE,
|
||||
code: DEFAULT_ERROR_CODE,
|
||||
position: error.position
|
||||
}];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
@ -112,7 +112,13 @@ describe('Collector', () => {
|
||||
__symbolic: 'class',
|
||||
decorators: [{
|
||||
__symbolic: 'call',
|
||||
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
|
||||
expression: {
|
||||
__symbolic: 'reference',
|
||||
module: 'angular2/core',
|
||||
name: 'Component',
|
||||
line: 4,
|
||||
character: 7
|
||||
},
|
||||
arguments: [{
|
||||
selector: 'my-hero-detail',
|
||||
template: `
|
||||
@ -132,8 +138,13 @@ describe('Collector', () => {
|
||||
__symbolic: 'property',
|
||||
decorators: [{
|
||||
__symbolic: 'call',
|
||||
expression:
|
||||
{__symbolic: 'reference', module: 'angular2/core', name: 'Input'}
|
||||
expression: {
|
||||
__symbolic: 'reference',
|
||||
module: 'angular2/core',
|
||||
name: 'Input',
|
||||
line: 18,
|
||||
character: 9
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}
|
||||
@ -153,7 +164,13 @@ describe('Collector', () => {
|
||||
__symbolic: 'class',
|
||||
decorators: [{
|
||||
__symbolic: 'call',
|
||||
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
|
||||
expression: {
|
||||
__symbolic: 'reference',
|
||||
module: 'angular2/core',
|
||||
name: 'Component',
|
||||
line: 9,
|
||||
character: 7
|
||||
},
|
||||
arguments: [{
|
||||
selector: 'my-app',
|
||||
template: `
|
||||
@ -172,20 +189,52 @@ describe('Collector', () => {
|
||||
__symbolic: 'reference',
|
||||
module: './hero-detail.component',
|
||||
name: 'HeroDetailComponent',
|
||||
line: 22,
|
||||
character: 21
|
||||
},
|
||||
{__symbolic: 'reference', module: 'angular2/common', name: 'NgFor'}
|
||||
{
|
||||
__symbolic: 'reference',
|
||||
module: 'angular2/common',
|
||||
name: 'NgFor',
|
||||
line: 22,
|
||||
character: 42
|
||||
}
|
||||
],
|
||||
providers: [{__symbolic: 'reference', module: './hero.service', default: true}],
|
||||
providers: [{
|
||||
__symbolic: 'reference',
|
||||
module: './hero.service',
|
||||
default: true,
|
||||
line: 23,
|
||||
character: 20
|
||||
}],
|
||||
pipes: [
|
||||
{__symbolic: 'reference', module: 'angular2/common', name: 'LowerCasePipe'},
|
||||
{__symbolic: 'reference', module: 'angular2/common', name: 'UpperCasePipe'}
|
||||
{
|
||||
__symbolic: 'reference',
|
||||
module: 'angular2/common',
|
||||
name: 'LowerCasePipe',
|
||||
line: 24,
|
||||
character: 16
|
||||
},
|
||||
{
|
||||
__symbolic: 'reference',
|
||||
module: 'angular2/common',
|
||||
name: 'UpperCasePipe',
|
||||
line: 24,
|
||||
character: 38
|
||||
}
|
||||
]
|
||||
}]
|
||||
}],
|
||||
members: {
|
||||
__ctor__: [{
|
||||
__symbolic: 'constructor',
|
||||
parameters: [{__symbolic: 'reference', module: './hero.service', default: true}]
|
||||
parameters: [{
|
||||
__symbolic: 'reference',
|
||||
module: './hero.service',
|
||||
default: true,
|
||||
line: 31,
|
||||
character: 42
|
||||
}]
|
||||
}],
|
||||
onSelect: [{__symbolic: 'method'}],
|
||||
ngOnInit: [{__symbolic: 'method'}],
|
||||
@ -236,22 +285,23 @@ describe('Collector', () => {
|
||||
});
|
||||
|
||||
it('should record annotations on set and get declarations', () => {
|
||||
const propertyData = {
|
||||
const propertyData = (line: number) => ({
|
||||
name: [{
|
||||
__symbolic: 'property',
|
||||
decorators: [{
|
||||
__symbolic: 'call',
|
||||
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Input'},
|
||||
expression:
|
||||
{__symbolic: 'reference', module: 'angular2/core', name: 'Input', line, character: 9},
|
||||
arguments: ['firstName']
|
||||
}]
|
||||
}]
|
||||
};
|
||||
});
|
||||
const caseGetProp = <ClassMetadata>casesMetadata.metadata['GetProp'];
|
||||
expect(caseGetProp.members).toEqual(propertyData);
|
||||
expect(caseGetProp.members).toEqual(propertyData(11));
|
||||
const caseSetProp = <ClassMetadata>casesMetadata.metadata['SetProp'];
|
||||
expect(caseSetProp.members).toEqual(propertyData);
|
||||
expect(caseSetProp.members).toEqual(propertyData(19));
|
||||
const caseFullProp = <ClassMetadata>casesMetadata.metadata['FullProp'];
|
||||
expect(caseFullProp.members).toEqual(propertyData);
|
||||
expect(caseFullProp.members).toEqual(propertyData(27));
|
||||
});
|
||||
|
||||
it('should record references to parameterized types', () => {
|
||||
@ -260,7 +310,13 @@ describe('Collector', () => {
|
||||
__symbolic: 'class',
|
||||
decorators: [{
|
||||
__symbolic: 'call',
|
||||
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Injectable'}
|
||||
expression: {
|
||||
__symbolic: 'reference',
|
||||
module: 'angular2/core',
|
||||
name: 'Injectable',
|
||||
line: 40,
|
||||
character: 7
|
||||
}
|
||||
}],
|
||||
members: {
|
||||
__ctor__: [{
|
||||
@ -313,7 +369,7 @@ describe('Collector', () => {
|
||||
const ctor = <ConstructorMetadata>someClass.members !['__ctor__'][0];
|
||||
const parameters = ctor.parameters;
|
||||
expect(parameters).toEqual([
|
||||
{__symbolic: 'reference', module: 'angular2/common', name: 'NgFor'}
|
||||
{__symbolic: 'reference', module: 'angular2/common', name: 'NgFor', line: 6, character: 29}
|
||||
]);
|
||||
});
|
||||
|
||||
@ -398,7 +454,7 @@ describe('Collector', () => {
|
||||
const ctor = <ConstructorMetadata>someClass.members !['__ctor__'][0];
|
||||
const parameters = ctor.parameters;
|
||||
expect(parameters).toEqual([
|
||||
{__symbolic: 'reference', module: 'angular2/common', name: 'NgFor'}
|
||||
{__symbolic: 'reference', module: 'angular2/common', name: 'NgFor', line: 6, character: 29}
|
||||
]);
|
||||
});
|
||||
|
||||
@ -427,7 +483,13 @@ describe('Collector', () => {
|
||||
B: 1,
|
||||
C: 30,
|
||||
D: 40,
|
||||
E: {__symbolic: 'reference', module: './exported-consts', name: 'constValue'}
|
||||
E: {
|
||||
__symbolic: 'reference',
|
||||
module: './exported-consts',
|
||||
name: 'constValue',
|
||||
line: 5,
|
||||
character: 75
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -457,13 +519,25 @@ describe('Collector', () => {
|
||||
expect(classData).toBeDefined();
|
||||
expect(classData.decorators).toEqual([{
|
||||
__symbolic: 'call',
|
||||
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
|
||||
expression: {
|
||||
__symbolic: 'reference',
|
||||
module: 'angular2/core',
|
||||
name: 'Component',
|
||||
line: 4,
|
||||
character: 5
|
||||
},
|
||||
arguments: [{
|
||||
providers: {
|
||||
__symbolic: 'call',
|
||||
expression: {
|
||||
__symbolic: 'select',
|
||||
expression: {__symbolic: 'reference', module: './static-method', name: 'MyModule'},
|
||||
expression: {
|
||||
__symbolic: 'reference',
|
||||
module: './static-method',
|
||||
name: 'MyModule',
|
||||
line: 5,
|
||||
character: 17
|
||||
},
|
||||
member: 'with'
|
||||
},
|
||||
arguments: ['a']
|
||||
@ -489,13 +563,25 @@ describe('Collector', () => {
|
||||
expect(classData).toBeDefined();
|
||||
expect(classData.decorators).toEqual([{
|
||||
__symbolic: 'call',
|
||||
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
|
||||
expression: {
|
||||
__symbolic: 'reference',
|
||||
module: 'angular2/core',
|
||||
name: 'Component',
|
||||
line: 4,
|
||||
character: 5
|
||||
},
|
||||
arguments: [{
|
||||
providers: [{
|
||||
provide: 'a',
|
||||
useValue: {
|
||||
__symbolic: 'select',
|
||||
expression: {__symbolic: 'reference', module: './static-field', name: 'MyModule'},
|
||||
expression: {
|
||||
__symbolic: 'reference',
|
||||
module: './static-field',
|
||||
name: 'MyModule',
|
||||
line: 5,
|
||||
character: 45
|
||||
},
|
||||
member: 'VALUE'
|
||||
}
|
||||
}]
|
||||
@ -578,8 +664,20 @@ describe('Collector', () => {
|
||||
const metadata = collector.getMetadata(source) !;
|
||||
expect(metadata.metadata).toEqual({
|
||||
MyClass: Object({__symbolic: 'class'}),
|
||||
OtherModule: {__symbolic: 'reference', module: './static-field-reference', name: 'Foo'},
|
||||
MyOtherModule: {__symbolic: 'reference', module: './static-field', name: 'MyModule'}
|
||||
OtherModule: {
|
||||
__symbolic: 'reference',
|
||||
module: './static-field-reference',
|
||||
name: 'Foo',
|
||||
line: 4,
|
||||
character: 12
|
||||
},
|
||||
MyOtherModule: {
|
||||
__symbolic: 'reference',
|
||||
module: './static-field',
|
||||
name: 'MyModule',
|
||||
line: 4,
|
||||
character: 25
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -598,7 +696,13 @@ describe('Collector', () => {
|
||||
__symbolic: 'class',
|
||||
decorators: [{
|
||||
__symbolic: 'call',
|
||||
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
|
||||
expression: {
|
||||
__symbolic: 'reference',
|
||||
module: 'angular2/core',
|
||||
name: 'Component',
|
||||
line: 11,
|
||||
character: 5
|
||||
},
|
||||
arguments: [{providers: [{__symbolic: 'reference', name: 'REQUIRED_VALIDATOR'}]}]
|
||||
}]
|
||||
}
|
||||
@ -620,7 +724,13 @@ describe('Collector', () => {
|
||||
__symbolic: 'class',
|
||||
decorators: [{
|
||||
__symbolic: 'call',
|
||||
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
|
||||
expression: {
|
||||
__symbolic: 'reference',
|
||||
module: 'angular2/core',
|
||||
name: 'Component',
|
||||
line: 11,
|
||||
character: 5
|
||||
},
|
||||
arguments: [{providers: [{__symbolic: 'reference', name: 'REQUIRED_VALIDATOR'}]}]
|
||||
}]
|
||||
}
|
||||
@ -653,7 +763,13 @@ describe('Collector', () => {
|
||||
__symbolic: 'constructor',
|
||||
parameterDecorators: [[{
|
||||
__symbolic: 'call',
|
||||
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Inject'},
|
||||
expression: {
|
||||
__symbolic: 'reference',
|
||||
module: 'angular2/core',
|
||||
name: 'Inject',
|
||||
line: 6,
|
||||
character: 19
|
||||
},
|
||||
arguments: ['a']
|
||||
}]],
|
||||
parameters: [{__symbolic: 'reference', name: 'any'}]
|
||||
@ -687,13 +803,20 @@ describe('Collector', () => {
|
||||
__symbolic: 'reference',
|
||||
module: './external',
|
||||
name: 'external',
|
||||
line: 0,
|
||||
character: 68,
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should simplify a redundant template', () => {
|
||||
e('`${external}`', 'import {external} from "./external";')
|
||||
.toEqual({__symbolic: 'reference', module: './external', name: 'external'});
|
||||
e('`${external}`', 'import {external} from "./external";').toEqual({
|
||||
__symbolic: 'reference',
|
||||
module: './external',
|
||||
name: 'external',
|
||||
line: 0,
|
||||
character: 59
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to collect complex template with imported references', () => {
|
||||
@ -710,11 +833,18 @@ describe('Collector', () => {
|
||||
__symbolic: 'binop',
|
||||
operator: '+',
|
||||
left: 'foo:',
|
||||
right: {__symbolic: 'reference', module: './external', name: 'foo'}
|
||||
right: {
|
||||
__symbolic: 'reference',
|
||||
module: './external',
|
||||
name: 'foo',
|
||||
line: 0,
|
||||
character: 63
|
||||
}
|
||||
},
|
||||
right: ', bar:'
|
||||
},
|
||||
right: {__symbolic: 'reference', module: './external', name: 'bar'}
|
||||
right:
|
||||
{__symbolic: 'reference', module: './external', name: 'bar', line: 0, character: 75}
|
||||
},
|
||||
right: ', end'
|
||||
});
|
||||
@ -741,11 +871,11 @@ describe('Collector', () => {
|
||||
__ctor__: [{
|
||||
__symbolic: 'constructor',
|
||||
parameters: [
|
||||
{__symbolic: 'reference', module: './foo', name: 'Foo'},
|
||||
{__symbolic: 'reference', module: './foo', name: 'Foo'},
|
||||
{__symbolic: 'reference', module: './foo', name: 'Foo'},
|
||||
{__symbolic: 'reference', module: './foo', name: 'Foo'},
|
||||
{__symbolic: 'reference', module: './foo', name: 'Foo'}
|
||||
{__symbolic: 'reference', module: './foo', name: 'Foo', line: 3, character: 24},
|
||||
{__symbolic: 'reference', module: './foo', name: 'Foo', line: 3, character: 24},
|
||||
{__symbolic: 'reference', module: './foo', name: 'Foo', line: 3, character: 24},
|
||||
{__symbolic: 'reference', module: './foo', name: 'Foo', line: 3, character: 24},
|
||||
{__symbolic: 'reference', module: './foo', name: 'Foo', line: 3, character: 24}
|
||||
]
|
||||
}]
|
||||
});
|
||||
@ -825,7 +955,9 @@ describe('Collector', () => {
|
||||
extends: {
|
||||
__symbolic: 'reference',
|
||||
module: './class-inheritance-parent',
|
||||
name: 'ParentClassFromOtherFile'
|
||||
name: 'ParentClassFromOtherFile',
|
||||
line: 9,
|
||||
character: 45,
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -149,12 +149,14 @@ describe('Evaluator', () => {
|
||||
const newExpression = program.getSourceFile('newExpression.ts');
|
||||
expect(evaluator.evaluateNode(findVarInitializer(newExpression, 'someValue'))).toEqual({
|
||||
__symbolic: 'new',
|
||||
expression: {__symbolic: 'reference', name: 'Value', module: './classes'},
|
||||
expression:
|
||||
{__symbolic: 'reference', name: 'Value', module: './classes', line: 4, character: 33},
|
||||
arguments: ['name', 12]
|
||||
});
|
||||
expect(evaluator.evaluateNode(findVarInitializer(newExpression, 'complex'))).toEqual({
|
||||
__symbolic: 'new',
|
||||
expression: {__symbolic: 'reference', name: 'Value', module: './classes'},
|
||||
expression:
|
||||
{__symbolic: 'reference', name: 'Value', module: './classes', line: 5, character: 42},
|
||||
arguments: ['name', 12]
|
||||
});
|
||||
});
|
||||
@ -173,8 +175,7 @@ describe('Evaluator', () => {
|
||||
const errors = program.getSourceFile('errors.ts');
|
||||
const fDecl = findVar(errors, 'f') !;
|
||||
expect(evaluator.evaluateNode(fDecl.initializer !))
|
||||
.toEqual(
|
||||
{__symbolic: 'error', message: 'Function call not supported', line: 1, character: 12});
|
||||
.toEqual({__symbolic: 'error', message: 'Lambda not supported', line: 1, character: 12});
|
||||
const eDecl = findVar(errors, 'e') !;
|
||||
expect(evaluator.evaluateNode(eDecl.type !)).toEqual({
|
||||
__symbolic: 'error',
|
||||
|
@ -184,8 +184,7 @@ describe('ngc transformer command-line', () => {
|
||||
|
||||
const exitCode = main(['-p', basePath], errorSpy);
|
||||
expect(errorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(errorSpy.calls.mostRecent().args[0])
|
||||
.toContain('Error at ' + path.join(basePath, 'mymodule.ts.MyComp.html'));
|
||||
expect(errorSpy.calls.mostRecent().args[0]).toContain('mymodule.ts.MyComp.html');
|
||||
expect(errorSpy.calls.mostRecent().args[0])
|
||||
.toContain(`Property 'unknownProp' does not exist on type 'MyComp'`);
|
||||
|
||||
@ -215,8 +214,7 @@ describe('ngc transformer command-line', () => {
|
||||
|
||||
const exitCode = main(['-p', basePath], errorSpy);
|
||||
expect(errorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(errorSpy.calls.mostRecent().args[0])
|
||||
.toContain('Error at ' + path.join(basePath, 'my.component.html(1,5):'));
|
||||
expect(errorSpy.calls.mostRecent().args[0]).toContain('my.component.html(1,5):');
|
||||
expect(errorSpy.calls.mostRecent().args[0])
|
||||
.toContain(`Property 'unknownProp' does not exist on type 'MyComp'`);
|
||||
|
||||
@ -1566,4 +1564,49 @@ describe('ngc transformer command-line', () => {
|
||||
expect(main(['-p', path.join(basePath, 'src/tsconfig.json')])).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatted messages', () => {
|
||||
it('should emit a formatted error message for a structural error', () => {
|
||||
write('src/tsconfig.json', `{
|
||||
"extends": "../tsconfig-base.json",
|
||||
"files": ["test-module.ts"]
|
||||
}`);
|
||||
write('src/lib/indirect2.ts', `
|
||||
declare var f: any;
|
||||
|
||||
export const t2 = f\`<p>hello</p>\`;
|
||||
`);
|
||||
write('src/lib/indirect1.ts', `
|
||||
import {t2} from './indirect2';
|
||||
export const t1 = t2 + ' ';
|
||||
`);
|
||||
write('src/lib/test.component.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
import {t1} from './indirect1';
|
||||
|
||||
@Component({
|
||||
template: t1,
|
||||
styleUrls: ['./test.component.css']
|
||||
})
|
||||
export class TestComponent {}
|
||||
`);
|
||||
write('src/test-module.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
import {TestComponent} from './lib/test.component';
|
||||
|
||||
@NgModule({declarations: [TestComponent]})
|
||||
export class TestModule {}
|
||||
`);
|
||||
const messages: string[] = [];
|
||||
const exitCode =
|
||||
main(['-p', path.join(basePath, 'src/tsconfig.json')], message => messages.push(message));
|
||||
expect(exitCode).toBe(1, 'Compile was expected to fail');
|
||||
expect(messages[0])
|
||||
.toEqual(`lib/test.component.ts(6,21): Error during template compile of 'TestComponent'
|
||||
Tagged template expressions are not supported in metadata in 't1'
|
||||
't1' references 't2' at lib/indirect1.ts(3,27)
|
||||
't2' contains the error at lib/indirect2.ts(4,27).
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -143,7 +143,7 @@ describe('perform watch', () => {
|
||||
|
||||
const errDiags = host.diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error);
|
||||
expect(errDiags.length).toBe(1);
|
||||
expect(errDiags[0].messageText).toContain('Function calls are not supported.');
|
||||
expect(errDiags[0].messageText).toContain('Function expressions are not supported');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -930,7 +930,7 @@ describe('ng program', () => {
|
||||
|
||||
const structuralErrors = program.getNgStructuralDiagnostics();
|
||||
expect(structuralErrors.length).toBe(1);
|
||||
expect(structuralErrors[0].messageText).toContain('Function calls are not supported.');
|
||||
expect(structuralErrors[0].messageText).toContain('Function expressions are not supported');
|
||||
});
|
||||
|
||||
it('should not throw on structural errors but collect them (loadNgStructureAsync)', (done) => {
|
||||
@ -943,7 +943,7 @@ describe('ng program', () => {
|
||||
program.loadNgStructureAsync().then(() => {
|
||||
const structuralErrors = program.getNgStructuralDiagnostics();
|
||||
expect(structuralErrors.length).toBe(1);
|
||||
expect(structuralErrors[0].messageText).toContain('Function calls are not supported.');
|
||||
expect(structuralErrors[0].messageText).toContain('Function expressions are not supported');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -982,7 +982,8 @@ describe('ng program', () => {
|
||||
const program = ng.createProgram({rootNames: allRootNames, options, host});
|
||||
const structuralErrors = program.getNgStructuralDiagnostics();
|
||||
expect(structuralErrors.length).toBe(1);
|
||||
expect(structuralErrors[0].messageText).toContain('Function calls are not supported.');
|
||||
expect(structuralErrors[0].messageText)
|
||||
.toContain('Function expressions are not supported');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user