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
60
packages/compiler/src/aot/formatted_error.ts
Normal file
60
packages/compiler/src/aot/formatted_error.ts
Normal file
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* 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 {syntaxError} from '../util';
|
||||
|
||||
export interface Position {
|
||||
fileName: string;
|
||||
line: number;
|
||||
column: number;
|
||||
}
|
||||
|
||||
export interface FormattedMessageChain {
|
||||
message: string;
|
||||
position?: Position;
|
||||
next?: FormattedMessageChain;
|
||||
}
|
||||
|
||||
export type FormattedError = Error & {
|
||||
chain: FormattedMessageChain;
|
||||
position?: Position;
|
||||
};
|
||||
|
||||
const FORMATTED_MESSAGE = 'ngFormattedMessage';
|
||||
|
||||
function indentStr(level: number): string {
|
||||
if (level <= 0) return '';
|
||||
if (level < 6) return ['', ' ', ' ', ' ', ' ', ' '][level];
|
||||
const half = indentStr(Math.floor(level / 2));
|
||||
return half + half + (level % 2 === 1 ? ' ' : '');
|
||||
}
|
||||
|
||||
function formatChain(chain: FormattedMessageChain | undefined, indent: number = 0): string {
|
||||
if (!chain) return '';
|
||||
const position = chain.position ?
|
||||
`${chain.position.fileName}(${chain.position.line+1},${chain.position.column+1})` :
|
||||
'';
|
||||
const prefix = position && indent === 0 ? `${position}: ` : '';
|
||||
const postfix = position && indent !== 0 ? ` at ${position}` : '';
|
||||
const message = `${prefix}${chain.message}${postfix}`;
|
||||
|
||||
return `${indentStr(indent)}${message}${(chain.next && ('\n' + formatChain(chain.next, indent + 2))) || ''}`;
|
||||
}
|
||||
|
||||
export function formattedError(chain: FormattedMessageChain): FormattedError {
|
||||
const message = formatChain(chain) + '.';
|
||||
const error = syntaxError(message) as FormattedError;
|
||||
(error as any)[FORMATTED_MESSAGE] = true;
|
||||
error.chain = chain;
|
||||
error.position = chain.position;
|
||||
return error;
|
||||
}
|
||||
|
||||
export function isFormattedError(error: Error): error is FormattedError {
|
||||
return !!(error as any)[FORMATTED_MESSAGE];
|
||||
}
|
@ -13,6 +13,7 @@ import * as o from '../output/output_ast';
|
||||
import {SummaryResolver} from '../summary_resolver';
|
||||
import {syntaxError} from '../util';
|
||||
|
||||
import {FormattedMessageChain, formattedError} from './formatted_error';
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
import {StaticSymbolResolver} from './static_symbol_resolver';
|
||||
|
||||
@ -98,11 +99,16 @@ export class StaticReflector implements CompileReflector {
|
||||
|
||||
findSymbolDeclaration(symbol: StaticSymbol): StaticSymbol {
|
||||
const resolvedSymbol = this.symbolResolver.resolveSymbol(symbol);
|
||||
if (resolvedSymbol && resolvedSymbol.metadata instanceof StaticSymbol) {
|
||||
return this.findSymbolDeclaration(resolvedSymbol.metadata);
|
||||
} else {
|
||||
return symbol;
|
||||
if (resolvedSymbol) {
|
||||
let resolvedMetadata = resolvedSymbol.metadata;
|
||||
if (resolvedMetadata && resolvedMetadata.__symbolic === 'resolved') {
|
||||
resolvedMetadata = resolvedMetadata.symbol;
|
||||
}
|
||||
if (resolvedMetadata instanceof StaticSymbol) {
|
||||
return this.findSymbolDeclaration(resolvedSymbol.metadata);
|
||||
}
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public annotations(type: StaticSymbol): any[] {
|
||||
@ -130,9 +136,12 @@ export class StaticReflector implements CompileReflector {
|
||||
(requiredType) => ownAnnotations.some(ann => requiredType.isTypeOf(ann)));
|
||||
if (!typeHasRequiredAnnotation) {
|
||||
this.reportError(
|
||||
syntaxError(
|
||||
`Class ${type.name} in ${type.filePath} extends from a ${CompileSummaryKind[summary.type.summaryKind!]} in another compilation unit without duplicating the decorator. ` +
|
||||
`Please add a ${requiredAnnotationTypes.map((type) => type.ngMetadataName).join(' or ')} decorator to the class.`),
|
||||
formatMetadataError(
|
||||
metadataError(
|
||||
`Class ${type.name} in ${type.filePath} extends from a ${CompileSummaryKind[summary.type.summaryKind!]} in another compilation unit without duplicating the decorator`,
|
||||
/* summary */ undefined,
|
||||
`Please add a ${requiredAnnotationTypes.map((type) => type.ngMetadataName).join(' or ')} decorator to the class`),
|
||||
type),
|
||||
type);
|
||||
}
|
||||
}
|
||||
@ -334,14 +343,6 @@ export class StaticReflector implements CompileReflector {
|
||||
return this.symbolResolver.getStaticSymbol(declarationFile, name, members);
|
||||
}
|
||||
|
||||
private reportError(error: Error, context: StaticSymbol, path?: string) {
|
||||
if (this.errorRecorder) {
|
||||
this.errorRecorder(error, (context && context.filePath) || path);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify but discard any errors
|
||||
*/
|
||||
@ -358,6 +359,7 @@ export class StaticReflector implements CompileReflector {
|
||||
const self = this;
|
||||
let scope = BindingScope.empty;
|
||||
const calling = new Map<StaticSymbol, boolean>();
|
||||
const rootContext = context;
|
||||
|
||||
function simplifyInContext(
|
||||
context: StaticSymbol, value: any, depth: number, references: number): any {
|
||||
@ -366,17 +368,64 @@ export class StaticReflector implements CompileReflector {
|
||||
return resolvedSymbol ? resolvedSymbol.metadata : null;
|
||||
}
|
||||
|
||||
function simplifyCall(functionSymbol: StaticSymbol, targetFunction: any, args: any[]) {
|
||||
function simplifyEagerly(value: any): any {
|
||||
return simplifyInContext(context, value, depth, 0);
|
||||
}
|
||||
|
||||
function simplifyLazily(value: any): any {
|
||||
return simplifyInContext(context, value, depth, references + 1);
|
||||
}
|
||||
|
||||
function simplifyNested(nestedContext: StaticSymbol, value: any): any {
|
||||
if (nestedContext === context) {
|
||||
// If the context hasn't changed let the exception propagate unmodified.
|
||||
return simplifyInContext(nestedContext, value, depth + 1, references);
|
||||
}
|
||||
try {
|
||||
return simplifyInContext(nestedContext, value, depth + 1, references);
|
||||
} catch (e) {
|
||||
if (isMetadataError(e)) {
|
||||
// Propagate the message text up but add a message to the chain that explains how we got
|
||||
// here.
|
||||
// e.chain implies e.symbol
|
||||
const summaryMsg = e.chain ? 'references \'' + e.symbol !.name + '\'' : errorSummary(e);
|
||||
const summary = `'${nestedContext.name}' ${summaryMsg}`;
|
||||
const chain = {message: summary, position: e.position, next: e.chain};
|
||||
// TODO(chuckj): retrieve the position information indirectly from the collectors node
|
||||
// map if the metadata is from a .ts file.
|
||||
self.error(
|
||||
{
|
||||
message: e.message,
|
||||
advise: e.advise,
|
||||
context: e.context, chain,
|
||||
symbol: nestedContext
|
||||
},
|
||||
context);
|
||||
} else {
|
||||
// It is probably an internal error.
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function simplifyCall(
|
||||
functionSymbol: StaticSymbol, targetFunction: any, args: any[], targetExpression: any) {
|
||||
if (targetFunction && targetFunction['__symbolic'] == 'function') {
|
||||
if (calling.get(functionSymbol)) {
|
||||
throw new Error('Recursion not supported');
|
||||
self.error(
|
||||
{
|
||||
message: 'Recursion is not supported',
|
||||
summary: `called '${functionSymbol.name}' recursively`,
|
||||
value: targetFunction
|
||||
},
|
||||
functionSymbol);
|
||||
}
|
||||
try {
|
||||
const value = targetFunction['value'];
|
||||
if (value && (depth != 0 || value.__symbolic != 'error')) {
|
||||
const parameters: string[] = targetFunction['parameters'];
|
||||
const defaults: any[] = targetFunction.defaults;
|
||||
args = args.map(arg => simplifyInContext(context, arg, depth + 1, references))
|
||||
args = args.map(arg => simplifyNested(context, arg))
|
||||
.map(arg => shouldIgnore(arg) ? undefined : arg);
|
||||
if (defaults && defaults.length > args.length) {
|
||||
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
|
||||
@ -390,7 +439,7 @@ export class StaticReflector implements CompileReflector {
|
||||
let result: any;
|
||||
try {
|
||||
scope = functionScope.done();
|
||||
result = simplifyInContext(functionSymbol, value, depth + 1, references);
|
||||
result = simplifyNested(functionSymbol, value);
|
||||
} finally {
|
||||
scope = oldScope;
|
||||
}
|
||||
@ -407,8 +456,22 @@ export class StaticReflector implements CompileReflector {
|
||||
// non-angular decorator, and we should just ignore it.
|
||||
return IGNORE;
|
||||
}
|
||||
return simplify(
|
||||
{__symbolic: 'error', message: 'Function call not supported', context: functionSymbol});
|
||||
let position: Position|undefined = undefined;
|
||||
if (targetExpression && targetExpression.__symbolic == 'resolved') {
|
||||
const line = targetExpression.line;
|
||||
const character = targetExpression.character;
|
||||
const fileName = targetExpression.fileName;
|
||||
if (fileName != null && line != null && character != null) {
|
||||
position = {fileName, line, column: character};
|
||||
}
|
||||
}
|
||||
self.error(
|
||||
{
|
||||
message: FUNCTION_CALL_NOT_SUPPORTED,
|
||||
context: functionSymbol,
|
||||
value: targetFunction, position
|
||||
},
|
||||
context);
|
||||
}
|
||||
|
||||
function simplify(expression: any): any {
|
||||
@ -422,7 +485,7 @@ export class StaticReflector implements CompileReflector {
|
||||
if (item && item.__symbolic === 'spread') {
|
||||
// We call with references as 0 because we require the actual value and cannot
|
||||
// tolerate a reference here.
|
||||
const spreadArray = simplifyInContext(context, item.expression, depth, 0);
|
||||
const spreadArray = simplifyEagerly(item.expression);
|
||||
if (Array.isArray(spreadArray)) {
|
||||
for (const spreadItem of spreadArray) {
|
||||
result.push(spreadItem);
|
||||
@ -448,7 +511,7 @@ export class StaticReflector implements CompileReflector {
|
||||
const staticSymbol = expression;
|
||||
const declarationValue = resolveReferenceValue(staticSymbol);
|
||||
if (declarationValue != null) {
|
||||
return simplifyInContext(staticSymbol, declarationValue, depth + 1, references);
|
||||
return simplifyNested(staticSymbol, declarationValue);
|
||||
} else {
|
||||
return staticSymbol;
|
||||
}
|
||||
@ -525,8 +588,8 @@ export class StaticReflector implements CompileReflector {
|
||||
}
|
||||
return null;
|
||||
case 'index':
|
||||
let indexTarget = simplifyInContext(context, expression['expression'], depth, 0);
|
||||
let index = simplifyInContext(context, expression['index'], depth, 0);
|
||||
let indexTarget = simplifyEagerly(expression['expression']);
|
||||
let index = simplifyEagerly(expression['index']);
|
||||
if (indexTarget && isPrimitive(index)) return indexTarget[index];
|
||||
return null;
|
||||
case 'select':
|
||||
@ -539,26 +602,41 @@ export class StaticReflector implements CompileReflector {
|
||||
self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
|
||||
const declarationValue = resolveReferenceValue(selectContext);
|
||||
if (declarationValue != null) {
|
||||
return simplifyInContext(
|
||||
selectContext, declarationValue, depth + 1, references);
|
||||
return simplifyNested(selectContext, declarationValue);
|
||||
} else {
|
||||
return selectContext;
|
||||
}
|
||||
}
|
||||
if (selectTarget && isPrimitive(member))
|
||||
return simplifyInContext(
|
||||
selectContext, selectTarget[member], depth + 1, references);
|
||||
return simplifyNested(selectContext, selectTarget[member]);
|
||||
return null;
|
||||
case 'reference':
|
||||
// Note: This only has to deal with variable references,
|
||||
// as symbol references have been converted into StaticSymbols already
|
||||
// in the StaticSymbolResolver!
|
||||
// Note: This only has to deal with variable references, as symbol references have
|
||||
// been converted into 'resolved'
|
||||
// in the StaticSymbolResolver.
|
||||
const name: string = expression['name'];
|
||||
const localValue = scope.resolve(name);
|
||||
if (localValue != BindingScope.missing) {
|
||||
return localValue;
|
||||
}
|
||||
break;
|
||||
case 'resolved':
|
||||
try {
|
||||
return simplify(expression.symbol);
|
||||
} catch (e) {
|
||||
// If an error is reported evaluating the symbol record the position of the
|
||||
// reference in the error so it can
|
||||
// be reported in the error message generated from the exception.
|
||||
if (isMetadataError(e) && expression.fileName != null &&
|
||||
expression.line != null && expression.character != null) {
|
||||
e.position = {
|
||||
fileName: expression.fileName,
|
||||
line: expression.line,
|
||||
column: expression.character
|
||||
};
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
case 'class':
|
||||
return context;
|
||||
case 'function':
|
||||
@ -580,29 +658,34 @@ export class StaticReflector implements CompileReflector {
|
||||
const argExpressions: any[] = expression['arguments'] || [];
|
||||
let converter = self.conversionMap.get(staticSymbol);
|
||||
if (converter) {
|
||||
const args =
|
||||
argExpressions
|
||||
.map(arg => simplifyInContext(context, arg, depth + 1, references))
|
||||
.map(arg => shouldIgnore(arg) ? undefined : arg);
|
||||
const args = argExpressions.map(arg => simplifyNested(context, arg))
|
||||
.map(arg => shouldIgnore(arg) ? undefined : arg);
|
||||
return converter(context, args);
|
||||
} else {
|
||||
// Determine if the function is one we can simplify.
|
||||
const targetFunction = resolveReferenceValue(staticSymbol);
|
||||
return simplifyCall(staticSymbol, targetFunction, argExpressions);
|
||||
return simplifyCall(
|
||||
staticSymbol, targetFunction, argExpressions, expression['expression']);
|
||||
}
|
||||
}
|
||||
return IGNORE;
|
||||
case 'error':
|
||||
let message = produceErrorMessage(expression);
|
||||
if (expression['line']) {
|
||||
message =
|
||||
`${message} (position ${expression['line']+1}:${expression['character']+1} in the original .ts file)`;
|
||||
self.reportError(
|
||||
positionalError(
|
||||
message, context.filePath, expression['line'], expression['character']),
|
||||
let message = expression.message;
|
||||
if (expression['line'] != null) {
|
||||
self.error(
|
||||
{
|
||||
message,
|
||||
context: expression.context,
|
||||
value: expression,
|
||||
position: {
|
||||
fileName: expression['fileName'],
|
||||
line: expression['line'],
|
||||
column: expression['character']
|
||||
}
|
||||
},
|
||||
context);
|
||||
} else {
|
||||
self.reportError(new Error(message), context);
|
||||
self.error({message, context: expression.context}, context);
|
||||
}
|
||||
return IGNORE;
|
||||
case 'ignore':
|
||||
@ -620,7 +703,7 @@ export class StaticReflector implements CompileReflector {
|
||||
return simplify(value);
|
||||
}
|
||||
}
|
||||
return simplifyInContext(context, value, depth, references + 1);
|
||||
return simplifyLazily(value);
|
||||
}
|
||||
return simplify(value);
|
||||
});
|
||||
@ -628,29 +711,19 @@ export class StaticReflector implements CompileReflector {
|
||||
return IGNORE;
|
||||
}
|
||||
|
||||
try {
|
||||
return simplify(value);
|
||||
} catch (e) {
|
||||
const members = context.members.length ? `.${context.members.join('.')}` : '';
|
||||
const message =
|
||||
`${e.message}, resolving symbol ${context.name}${members} in ${context.filePath}`;
|
||||
if (e.fileName) {
|
||||
throw positionalError(message, e.fileName, e.line, e.column);
|
||||
}
|
||||
throw syntaxError(message);
|
||||
}
|
||||
return simplify(value);
|
||||
}
|
||||
|
||||
const recordedSimplifyInContext = (context: StaticSymbol, value: any) => {
|
||||
try {
|
||||
return simplifyInContext(context, value, 0, 0);
|
||||
} catch (e) {
|
||||
let result: any;
|
||||
try {
|
||||
result = simplifyInContext(context, value, 0, 0);
|
||||
} catch (e) {
|
||||
if (this.errorRecorder) {
|
||||
this.reportError(e, context);
|
||||
} else {
|
||||
throw formatMetadataError(e, context);
|
||||
}
|
||||
};
|
||||
|
||||
const result = this.errorRecorder ? recordedSimplifyInContext(context, value) :
|
||||
simplifyInContext(context, value, 0, 0);
|
||||
}
|
||||
if (shouldIgnore(result)) {
|
||||
return undefined;
|
||||
}
|
||||
@ -662,40 +735,166 @@ export class StaticReflector implements CompileReflector {
|
||||
return resolvedSymbol && resolvedSymbol.metadata ? resolvedSymbol.metadata :
|
||||
{__symbolic: 'class'};
|
||||
}
|
||||
}
|
||||
|
||||
function expandedMessage(error: any): string {
|
||||
switch (error.message) {
|
||||
case 'Reference to non-exported class':
|
||||
if (error.context && error.context.className) {
|
||||
return `Reference to a non-exported class ${error.context.className}. Consider exporting the class`;
|
||||
}
|
||||
break;
|
||||
case 'Variable not initialized':
|
||||
return 'Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler';
|
||||
case 'Destructuring not supported':
|
||||
return 'Referencing an exported destructured variable or constant is not supported by the template compiler. Consider simplifying this to avoid destructuring';
|
||||
case 'Could not resolve type':
|
||||
if (error.context && error.context.typeName) {
|
||||
return `Could not resolve type ${error.context.typeName}`;
|
||||
}
|
||||
break;
|
||||
case 'Function call not supported':
|
||||
let prefix =
|
||||
error.context && error.context.name ? `Calling function '${error.context.name}', f` : 'F';
|
||||
return prefix +
|
||||
'unction calls are not supported. Consider replacing the function or lambda with a reference to an exported function';
|
||||
case 'Reference to a local symbol':
|
||||
if (error.context && error.context.name) {
|
||||
return `Reference to a local (non-exported) symbol '${error.context.name}'. Consider exporting the symbol`;
|
||||
}
|
||||
break;
|
||||
private reportError(error: Error, context: StaticSymbol, path?: string) {
|
||||
if (this.errorRecorder) {
|
||||
this.errorRecorder(
|
||||
formatMetadataError(error, context), (context && context.filePath) || path);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private error(
|
||||
{message, summary, advise, position, context, value, symbol, chain}: {
|
||||
message: string,
|
||||
summary?: string,
|
||||
advise?: string,
|
||||
position?: Position,
|
||||
context?: any,
|
||||
value?: any,
|
||||
symbol?: StaticSymbol,
|
||||
chain?: MetadataMessageChain
|
||||
},
|
||||
reportingContext: StaticSymbol) {
|
||||
this.reportError(
|
||||
metadataError(message, summary, advise, position, symbol, context, chain),
|
||||
reportingContext);
|
||||
}
|
||||
return error.message;
|
||||
}
|
||||
|
||||
function produceErrorMessage(error: any): string {
|
||||
return `Error encountered resolving symbol values statically. ${expandedMessage(error)}`;
|
||||
interface Position {
|
||||
fileName: string;
|
||||
line: number;
|
||||
column: number;
|
||||
}
|
||||
|
||||
interface MetadataMessageChain {
|
||||
message: string;
|
||||
summary?: string;
|
||||
position?: Position;
|
||||
context?: any;
|
||||
symbol?: StaticSymbol;
|
||||
next?: MetadataMessageChain;
|
||||
}
|
||||
|
||||
type MetadataError = Error & {
|
||||
position?: Position;
|
||||
advise?: string;
|
||||
summary?: string;
|
||||
context?: any;
|
||||
symbol?: StaticSymbol;
|
||||
chain?: MetadataMessageChain;
|
||||
};
|
||||
|
||||
const METADATA_ERROR = 'ngMetadataError';
|
||||
|
||||
function metadataError(
|
||||
message: string, summary?: string, advise?: string, position?: Position, symbol?: StaticSymbol,
|
||||
context?: any, chain?: MetadataMessageChain): MetadataError {
|
||||
const error = syntaxError(message) as MetadataError;
|
||||
(error as any)[METADATA_ERROR] = true;
|
||||
if (advise) error.advise = advise;
|
||||
if (position) error.position = position;
|
||||
if (summary) error.summary = summary;
|
||||
if (context) error.context = context;
|
||||
if (chain) error.chain = chain;
|
||||
if (symbol) error.symbol = symbol;
|
||||
return error;
|
||||
}
|
||||
|
||||
function isMetadataError(error: Error): error is MetadataError {
|
||||
return !!(error as any)[METADATA_ERROR];
|
||||
}
|
||||
|
||||
const REFERENCE_TO_NONEXPORTED_CLASS = 'Reference to non-exported class';
|
||||
const VARIABLE_NOT_INITIALIZED = 'Variable not initialized';
|
||||
const DESTRUCTURE_NOT_SUPPORTED = 'Destructuring not supported';
|
||||
const COULD_NOT_RESOLVE_TYPE = 'Could not resolve type';
|
||||
const FUNCTION_CALL_NOT_SUPPORTED = 'Function call not supported';
|
||||
const REFERENCE_TO_LOCAL_SYMBOL = 'Reference to a local symbol';
|
||||
const LAMBDA_NOT_SUPPORTED = 'Lambda not supported';
|
||||
|
||||
function expandedMessage(message: string, context: any): string {
|
||||
switch (message) {
|
||||
case REFERENCE_TO_NONEXPORTED_CLASS:
|
||||
if (context && context.className) {
|
||||
return `References to a non-exported class are not supported in decorators but ${context.className} was referenced.`;
|
||||
}
|
||||
break;
|
||||
case VARIABLE_NOT_INITIALIZED:
|
||||
return 'Only initialized variables and constants can be referenced in decorators because the value of this variable is needed by the template compiler';
|
||||
case DESTRUCTURE_NOT_SUPPORTED:
|
||||
return 'Referencing an exported destructured variable or constant is not supported in decorators and this value is needed by the template compiler';
|
||||
case COULD_NOT_RESOLVE_TYPE:
|
||||
if (context && context.typeName) {
|
||||
return `Could not resolve type ${context.typeName}`;
|
||||
}
|
||||
break;
|
||||
case FUNCTION_CALL_NOT_SUPPORTED:
|
||||
if (context && context.name) {
|
||||
return `Function calls are not supported in decorators but '${context.name}' was called`;
|
||||
}
|
||||
return 'Function calls are not supported in decorators';
|
||||
case REFERENCE_TO_LOCAL_SYMBOL:
|
||||
if (context && context.name) {
|
||||
return `Reference to a local (non-exported) symbols are not supported in decorators but '${context.name}' was referenced`;
|
||||
}
|
||||
break;
|
||||
case LAMBDA_NOT_SUPPORTED:
|
||||
return `Function expressions are not supported in decorators`;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
function messageAdvise(message: string, context: any): string|undefined {
|
||||
switch (message) {
|
||||
case REFERENCE_TO_NONEXPORTED_CLASS:
|
||||
if (context && context.className) {
|
||||
return `Consider exporting '${context.className}'`;
|
||||
}
|
||||
break;
|
||||
case DESTRUCTURE_NOT_SUPPORTED:
|
||||
return 'Consider simplifying to avoid destructuring';
|
||||
case REFERENCE_TO_LOCAL_SYMBOL:
|
||||
if (context && context.name) {
|
||||
return `Consider exporting '${context.name}'`;
|
||||
}
|
||||
break;
|
||||
case LAMBDA_NOT_SUPPORTED:
|
||||
return `Consider changing the function expression into an exported function`;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function errorSummary(error: MetadataError): string {
|
||||
if (error.summary) {
|
||||
return error.summary;
|
||||
}
|
||||
switch (error.message) {
|
||||
case REFERENCE_TO_NONEXPORTED_CLASS:
|
||||
if (error.context && error.context.className) {
|
||||
return `references non-exported class ${error.context.className}`;
|
||||
}
|
||||
break;
|
||||
case VARIABLE_NOT_INITIALIZED:
|
||||
return 'is not initialized';
|
||||
case DESTRUCTURE_NOT_SUPPORTED:
|
||||
return 'is a destructured variable';
|
||||
case COULD_NOT_RESOLVE_TYPE:
|
||||
return 'could not be resolved';
|
||||
case FUNCTION_CALL_NOT_SUPPORTED:
|
||||
if (error.context && error.context.name) {
|
||||
return `calls '${error.context.name}'`;
|
||||
}
|
||||
return `calls a function`;
|
||||
case REFERENCE_TO_LOCAL_SYMBOL:
|
||||
if (error.context && error.context.name) {
|
||||
return `references local variable ${error.context.name}`;
|
||||
}
|
||||
return `references a local variable`;
|
||||
}
|
||||
return 'contains the error';
|
||||
}
|
||||
|
||||
function mapStringMap(input: {[key: string]: any}, transform: (value: any, key: string) => any):
|
||||
@ -751,10 +950,30 @@ class PopulatedScope extends BindingScope {
|
||||
}
|
||||
}
|
||||
|
||||
function positionalError(message: string, fileName: string, line: number, column: number): Error {
|
||||
const result = syntaxError(message);
|
||||
(result as any).fileName = fileName;
|
||||
(result as any).line = line;
|
||||
(result as any).column = column;
|
||||
return result;
|
||||
}
|
||||
function formatMetadataMessageChain(
|
||||
chain: MetadataMessageChain, advise: string | undefined): FormattedMessageChain {
|
||||
const expanded = expandedMessage(chain.message, chain.context);
|
||||
const nesting = chain.symbol ? ` in '${chain.symbol.name}'` : '';
|
||||
const message = `${expanded}${nesting}`;
|
||||
const position = chain.position;
|
||||
const next: FormattedMessageChain|undefined = chain.next ?
|
||||
formatMetadataMessageChain(chain.next, advise) :
|
||||
advise ? {message: advise} : undefined;
|
||||
return {message, position, next};
|
||||
}
|
||||
|
||||
function formatMetadataError(e: Error, context: StaticSymbol): Error {
|
||||
if (isMetadataError(e)) {
|
||||
// Produce a formatted version of the and leaving enough information in the original error
|
||||
// to recover the formatting information to eventually produce a diagnostic error message.
|
||||
const position = e.position;
|
||||
const chain: MetadataMessageChain = {
|
||||
message: `Error during template compile of '${context.name}'`,
|
||||
position: position,
|
||||
next: {message: e.message, next: e.chain, context: e.context, symbol: e.symbol}
|
||||
};
|
||||
const advise = e.advise || messageAdvise(e.message, e.context);
|
||||
return formattedError(formatMetadataMessageChain(chain, advise));
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
@ -146,9 +146,9 @@ export class StaticSymbolResolver {
|
||||
if (isGeneratedFile(staticSymbol.filePath)) {
|
||||
return null;
|
||||
}
|
||||
let resolvedSymbol = this.resolveSymbol(staticSymbol);
|
||||
let resolvedSymbol = unwrapResolvedMetadata(this.resolveSymbol(staticSymbol));
|
||||
while (resolvedSymbol && resolvedSymbol.metadata instanceof StaticSymbol) {
|
||||
resolvedSymbol = this.resolveSymbol(resolvedSymbol.metadata);
|
||||
resolvedSymbol = unwrapResolvedMetadata(this.resolveSymbol(resolvedSymbol.metadata));
|
||||
}
|
||||
return (resolvedSymbol && resolvedSymbol.metadata && resolvedSymbol.metadata.arity) || null;
|
||||
}
|
||||
@ -204,7 +204,7 @@ export class StaticSymbolResolver {
|
||||
if (!baseResolvedSymbol) {
|
||||
return null;
|
||||
}
|
||||
const baseMetadata = baseResolvedSymbol.metadata;
|
||||
let baseMetadata = unwrapResolvedMetadata(baseResolvedSymbol.metadata);
|
||||
if (baseMetadata instanceof StaticSymbol) {
|
||||
return new ResolvedStaticSymbol(
|
||||
staticSymbol, this.getStaticSymbol(baseMetadata.filePath, baseMetadata.name, members));
|
||||
@ -374,6 +374,19 @@ export class StaticSymbolResolver {
|
||||
return new ResolvedStaticSymbol(sourceSymbol, transformedMeta);
|
||||
}
|
||||
|
||||
let _originalFileMemo: string|undefined;
|
||||
const getOriginalName: () => string = () => {
|
||||
if (!_originalFileMemo) {
|
||||
// Guess what hte original file name is from the reference. If it has a `.d.ts` extension
|
||||
// replace it with `.ts`. If it already has `.ts` just leave it in place. If it doesn't have
|
||||
// .ts or .d.ts, append `.ts'. Also, if it is in `node_modules`, trim the `node_module`
|
||||
// location as it is not important to finding the file.
|
||||
_originalFileMemo =
|
||||
topLevelPath.replace(/((\.ts)|(\.d\.ts)|)$/, '.ts').replace(/^.*node_modules[/\\]/, '');
|
||||
}
|
||||
return _originalFileMemo;
|
||||
};
|
||||
|
||||
const self = this;
|
||||
|
||||
class ReferenceTransformer extends ValueTransformer {
|
||||
@ -397,10 +410,19 @@ export class StaticSymbolResolver {
|
||||
if (!filePath) {
|
||||
return {
|
||||
__symbolic: 'error',
|
||||
message: `Could not resolve ${module} relative to ${sourceSymbol.filePath}.`
|
||||
message: `Could not resolve ${module} relative to ${sourceSymbol.filePath}.`,
|
||||
line: map.line,
|
||||
character: map.character,
|
||||
fileName: getOriginalName()
|
||||
};
|
||||
}
|
||||
return self.getStaticSymbol(filePath, name);
|
||||
return {
|
||||
__symbolic: 'resolved',
|
||||
symbol: self.getStaticSymbol(filePath, name),
|
||||
line: map.line,
|
||||
character: map.character,
|
||||
fileName: getOriginalName()
|
||||
};
|
||||
} else if (functionParams.indexOf(name) >= 0) {
|
||||
// reference to a function parameter
|
||||
return {__symbolic: 'reference', name: name};
|
||||
@ -411,14 +433,17 @@ export class StaticSymbolResolver {
|
||||
// ambient value
|
||||
null;
|
||||
}
|
||||
} else if (symbolic === 'error') {
|
||||
return {...map, fileName: getOriginalName()};
|
||||
} else {
|
||||
return super.visitStringMap(map, functionParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
const transformedMeta = visitValue(metadata, new ReferenceTransformer(), []);
|
||||
if (transformedMeta instanceof StaticSymbol) {
|
||||
return this.createExport(sourceSymbol, transformedMeta);
|
||||
let unwrappedTransformedMeta = unwrapResolvedMetadata(transformedMeta);
|
||||
if (unwrappedTransformedMeta instanceof StaticSymbol) {
|
||||
return this.createExport(sourceSymbol, unwrappedTransformedMeta);
|
||||
}
|
||||
return new ResolvedStaticSymbol(sourceSymbol, transformedMeta);
|
||||
}
|
||||
@ -505,3 +530,10 @@ export class StaticSymbolResolver {
|
||||
export function unescapeIdentifier(identifier: string): string {
|
||||
return identifier.startsWith('___') ? identifier.substr(1) : identifier;
|
||||
}
|
||||
|
||||
export function unwrapResolvedMetadata(metadata: any): any {
|
||||
if (metadata && metadata.__symbolic === 'resolved') {
|
||||
return metadata.symbol;
|
||||
}
|
||||
return metadata;
|
||||
}
|
@ -35,6 +35,7 @@ export * from './aot/compiler';
|
||||
export * from './aot/generated_file';
|
||||
export * from './aot/compiler_options';
|
||||
export * from './aot/compiler_host';
|
||||
export * from './aot/formatted_error';
|
||||
export * from './aot/static_reflector';
|
||||
export * from './aot/static_symbol';
|
||||
export * from './aot/static_symbol_resolver';
|
||||
|
Reference in New Issue
Block a user