1
|
"use strict";
|
2
|
/**
|
3
|
* @license
|
4
|
* Copyright Google Inc. All Rights Reserved.
|
5
|
*
|
6
|
* Use of this source code is governed by an MIT-style license that can be
|
7
|
* found in the LICENSE file at https://angular.io/license
|
8
|
*/
|
9
|
var __extends = (this && this.__extends) || (function () {
|
10
|
var extendStatics = Object.setPrototypeOf ||
|
11
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
12
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
13
|
return function (d, b) {
|
14
|
extendStatics(d, b);
|
15
|
function __() { this.constructor = d; }
|
16
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
17
|
};
|
18
|
})();
|
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
20
|
var compiler_1 = require("@angular/compiler");
|
21
|
var expression_type_1 = require("./expression_type");
|
22
|
var symbols_1 = require("./symbols");
|
23
|
function getTemplateExpressionDiagnostics(info) {
|
24
|
var visitor = new ExpressionDiagnosticsVisitor(info, function (path, includeEvent) {
|
25
|
return getExpressionScope(info, path, includeEvent);
|
26
|
});
|
27
|
compiler_1.templateVisitAll(visitor, info.templateAst);
|
28
|
return visitor.diagnostics;
|
29
|
}
|
30
|
exports.getTemplateExpressionDiagnostics = getTemplateExpressionDiagnostics;
|
31
|
function getExpressionDiagnostics(scope, ast, query, context) {
|
32
|
if (context === void 0) { context = {}; }
|
33
|
var analyzer = new expression_type_1.AstType(scope, query, context);
|
34
|
analyzer.getDiagnostics(ast);
|
35
|
return analyzer.diagnostics;
|
36
|
}
|
37
|
exports.getExpressionDiagnostics = getExpressionDiagnostics;
|
38
|
function getReferences(info) {
|
39
|
var result = [];
|
40
|
function processReferences(references) {
|
41
|
var _loop_1 = function (reference) {
|
42
|
var type = undefined;
|
43
|
if (reference.value) {
|
44
|
type = info.query.getTypeSymbol(compiler_1.tokenReference(reference.value));
|
45
|
}
|
46
|
result.push({
|
47
|
name: reference.name,
|
48
|
kind: 'reference',
|
49
|
type: type || info.query.getBuiltinType(symbols_1.BuiltinType.Any),
|
50
|
get definition() { return getDefintionOf(info, reference); }
|
51
|
});
|
52
|
};
|
53
|
for (var _i = 0, references_1 = references; _i < references_1.length; _i++) {
|
54
|
var reference = references_1[_i];
|
55
|
_loop_1(reference);
|
56
|
}
|
57
|
}
|
58
|
var visitor = new (function (_super) {
|
59
|
__extends(class_1, _super);
|
60
|
function class_1() {
|
61
|
return _super !== null && _super.apply(this, arguments) || this;
|
62
|
}
|
63
|
class_1.prototype.visitEmbeddedTemplate = function (ast, context) {
|
64
|
_super.prototype.visitEmbeddedTemplate.call(this, ast, context);
|
65
|
processReferences(ast.references);
|
66
|
};
|
67
|
class_1.prototype.visitElement = function (ast, context) {
|
68
|
_super.prototype.visitElement.call(this, ast, context);
|
69
|
processReferences(ast.references);
|
70
|
};
|
71
|
return class_1;
|
72
|
}(compiler_1.RecursiveTemplateAstVisitor));
|
73
|
compiler_1.templateVisitAll(visitor, info.templateAst);
|
74
|
return result;
|
75
|
}
|
76
|
function getDefintionOf(info, ast) {
|
77
|
if (info.fileName) {
|
78
|
var templateOffset = info.offset;
|
79
|
return [{
|
80
|
fileName: info.fileName,
|
81
|
span: {
|
82
|
start: ast.sourceSpan.start.offset + templateOffset,
|
83
|
end: ast.sourceSpan.end.offset + templateOffset
|
84
|
}
|
85
|
}];
|
86
|
}
|
87
|
}
|
88
|
function getVarDeclarations(info, path) {
|
89
|
var result = [];
|
90
|
var current = path.tail;
|
91
|
while (current) {
|
92
|
if (current instanceof compiler_1.EmbeddedTemplateAst) {
|
93
|
var _loop_2 = function (variable) {
|
94
|
var name_1 = variable.name;
|
95
|
// Find the first directive with a context.
|
96
|
var context = current.directives.map(function (d) { return info.query.getTemplateContext(d.directive.type.reference); })
|
97
|
.find(function (c) { return !!c; });
|
98
|
// Determine the type of the context field referenced by variable.value.
|
99
|
var type = undefined;
|
100
|
if (context) {
|
101
|
var value = context.get(variable.value);
|
102
|
if (value) {
|
103
|
type = value.type;
|
104
|
var kind = info.query.getTypeKind(type);
|
105
|
if (kind === symbols_1.BuiltinType.Any || kind == symbols_1.BuiltinType.Unbound) {
|
106
|
// The any type is not very useful here. For special cases, such as ngFor, we can do
|
107
|
// better.
|
108
|
type = refinedVariableType(type, info, current);
|
109
|
}
|
110
|
}
|
111
|
}
|
112
|
if (!type) {
|
113
|
type = info.query.getBuiltinType(symbols_1.BuiltinType.Any);
|
114
|
}
|
115
|
result.push({
|
116
|
name: name_1,
|
117
|
kind: 'variable', type: type, get definition() { return getDefintionOf(info, variable); }
|
118
|
});
|
119
|
};
|
120
|
for (var _i = 0, _a = current.variables; _i < _a.length; _i++) {
|
121
|
var variable = _a[_i];
|
122
|
_loop_2(variable);
|
123
|
}
|
124
|
}
|
125
|
current = path.parentOf(current);
|
126
|
}
|
127
|
return result;
|
128
|
}
|
129
|
function refinedVariableType(type, info, templateElement) {
|
130
|
// Special case the ngFor directive
|
131
|
var ngForDirective = templateElement.directives.find(function (d) {
|
132
|
var name = compiler_1.identifierName(d.directive.type);
|
133
|
return name == 'NgFor' || name == 'NgForOf';
|
134
|
});
|
135
|
if (ngForDirective) {
|
136
|
var ngForOfBinding = ngForDirective.inputs.find(function (i) { return i.directiveName == 'ngForOf'; });
|
137
|
if (ngForOfBinding) {
|
138
|
var bindingType = new expression_type_1.AstType(info.members, info.query, {}).getType(ngForOfBinding.value);
|
139
|
if (bindingType) {
|
140
|
var result = info.query.getElementType(bindingType);
|
141
|
if (result) {
|
142
|
return result;
|
143
|
}
|
144
|
}
|
145
|
}
|
146
|
}
|
147
|
// We can't do better, return any
|
148
|
return info.query.getBuiltinType(symbols_1.BuiltinType.Any);
|
149
|
}
|
150
|
function getEventDeclaration(info, includeEvent) {
|
151
|
var result = [];
|
152
|
if (includeEvent) {
|
153
|
// TODO: Determine the type of the event parameter based on the Observable<T> or EventEmitter<T>
|
154
|
// of the event.
|
155
|
result = [{ name: '$event', kind: 'variable', type: info.query.getBuiltinType(symbols_1.BuiltinType.Any) }];
|
156
|
}
|
157
|
return result;
|
158
|
}
|
159
|
function getExpressionScope(info, path, includeEvent) {
|
160
|
var result = info.members;
|
161
|
var references = getReferences(info);
|
162
|
var variables = getVarDeclarations(info, path);
|
163
|
var events = getEventDeclaration(info, includeEvent);
|
164
|
if (references.length || variables.length || events.length) {
|
165
|
var referenceTable = info.query.createSymbolTable(references);
|
166
|
var variableTable = info.query.createSymbolTable(variables);
|
167
|
var eventsTable = info.query.createSymbolTable(events);
|
168
|
result = info.query.mergeSymbolTable([result, referenceTable, variableTable, eventsTable]);
|
169
|
}
|
170
|
return result;
|
171
|
}
|
172
|
exports.getExpressionScope = getExpressionScope;
|
173
|
var ExpressionDiagnosticsVisitor = (function (_super) {
|
174
|
__extends(ExpressionDiagnosticsVisitor, _super);
|
175
|
function ExpressionDiagnosticsVisitor(info, getExpressionScope) {
|
176
|
var _this = _super.call(this) || this;
|
177
|
_this.info = info;
|
178
|
_this.getExpressionScope = getExpressionScope;
|
179
|
_this.diagnostics = [];
|
180
|
_this.path = new compiler_1.AstPath([]);
|
181
|
return _this;
|
182
|
}
|
183
|
ExpressionDiagnosticsVisitor.prototype.visitDirective = function (ast, context) {
|
184
|
// Override the default child visitor to ignore the host properties of a directive.
|
185
|
if (ast.inputs && ast.inputs.length) {
|
186
|
compiler_1.templateVisitAll(this, ast.inputs, context);
|
187
|
}
|
188
|
};
|
189
|
ExpressionDiagnosticsVisitor.prototype.visitBoundText = function (ast) {
|
190
|
this.push(ast);
|
191
|
this.diagnoseExpression(ast.value, ast.sourceSpan.start.offset, false);
|
192
|
this.pop();
|
193
|
};
|
194
|
ExpressionDiagnosticsVisitor.prototype.visitDirectiveProperty = function (ast) {
|
195
|
this.push(ast);
|
196
|
this.diagnoseExpression(ast.value, this.attributeValueLocation(ast), false);
|
197
|
this.pop();
|
198
|
};
|
199
|
ExpressionDiagnosticsVisitor.prototype.visitElementProperty = function (ast) {
|
200
|
this.push(ast);
|
201
|
this.diagnoseExpression(ast.value, this.attributeValueLocation(ast), false);
|
202
|
this.pop();
|
203
|
};
|
204
|
ExpressionDiagnosticsVisitor.prototype.visitEvent = function (ast) {
|
205
|
this.push(ast);
|
206
|
this.diagnoseExpression(ast.handler, this.attributeValueLocation(ast), true);
|
207
|
this.pop();
|
208
|
};
|
209
|
ExpressionDiagnosticsVisitor.prototype.visitVariable = function (ast) {
|
210
|
var directive = this.directiveSummary;
|
211
|
if (directive && ast.value) {
|
212
|
var context = this.info.query.getTemplateContext(directive.type.reference);
|
213
|
if (context && !context.has(ast.value)) {
|
214
|
if (ast.value === '$implicit') {
|
215
|
this.reportError('The template context does not have an implicit value', spanOf(ast.sourceSpan));
|
216
|
}
|
217
|
else {
|
218
|
this.reportError("The template context does not defined a member called '" + ast.value + "'", spanOf(ast.sourceSpan));
|
219
|
}
|
220
|
}
|
221
|
}
|
222
|
};
|
223
|
ExpressionDiagnosticsVisitor.prototype.visitElement = function (ast, context) {
|
224
|
this.push(ast);
|
225
|
_super.prototype.visitElement.call(this, ast, context);
|
226
|
this.pop();
|
227
|
};
|
228
|
ExpressionDiagnosticsVisitor.prototype.visitEmbeddedTemplate = function (ast, context) {
|
229
|
var previousDirectiveSummary = this.directiveSummary;
|
230
|
this.push(ast);
|
231
|
// Find directive that refernces this template
|
232
|
this.directiveSummary =
|
233
|
ast.directives.map(function (d) { return d.directive; }).find(function (d) { return hasTemplateReference(d.type); });
|
234
|
// Process children
|
235
|
_super.prototype.visitEmbeddedTemplate.call(this, ast, context);
|
236
|
this.pop();
|
237
|
this.directiveSummary = previousDirectiveSummary;
|
238
|
};
|
239
|
ExpressionDiagnosticsVisitor.prototype.attributeValueLocation = function (ast) {
|
240
|
var path = compiler_1.findNode(this.info.htmlAst, ast.sourceSpan.start.offset);
|
241
|
var last = path.tail;
|
242
|
if (last instanceof compiler_1.Attribute && last.valueSpan) {
|
243
|
// Add 1 for the quote.
|
244
|
return last.valueSpan.start.offset + 1;
|
245
|
}
|
246
|
return ast.sourceSpan.start.offset;
|
247
|
};
|
248
|
ExpressionDiagnosticsVisitor.prototype.diagnoseExpression = function (ast, offset, includeEvent) {
|
249
|
var _this = this;
|
250
|
var scope = this.getExpressionScope(this.path, includeEvent);
|
251
|
(_a = this.diagnostics).push.apply(_a, getExpressionDiagnostics(scope, ast, this.info.query, {
|
252
|
event: includeEvent
|
253
|
}).map(function (d) { return ({
|
254
|
span: offsetSpan(d.ast.span, offset + _this.info.offset),
|
255
|
kind: d.kind,
|
256
|
message: d.message
|
257
|
}); }));
|
258
|
var _a;
|
259
|
};
|
260
|
ExpressionDiagnosticsVisitor.prototype.push = function (ast) { this.path.push(ast); };
|
261
|
ExpressionDiagnosticsVisitor.prototype.pop = function () { this.path.pop(); };
|
262
|
ExpressionDiagnosticsVisitor.prototype.reportError = function (message, span) {
|
263
|
if (span) {
|
264
|
this.diagnostics.push({ span: offsetSpan(span, this.info.offset), kind: expression_type_1.DiagnosticKind.Error, message: message });
|
265
|
}
|
266
|
};
|
267
|
ExpressionDiagnosticsVisitor.prototype.reportWarning = function (message, span) {
|
268
|
this.diagnostics.push({ span: offsetSpan(span, this.info.offset), kind: expression_type_1.DiagnosticKind.Warning, message: message });
|
269
|
};
|
270
|
return ExpressionDiagnosticsVisitor;
|
271
|
}(compiler_1.RecursiveTemplateAstVisitor));
|
272
|
function hasTemplateReference(type) {
|
273
|
if (type.diDeps) {
|
274
|
for (var _i = 0, _a = type.diDeps; _i < _a.length; _i++) {
|
275
|
var diDep = _a[_i];
|
276
|
if (diDep.token && diDep.token.identifier &&
|
277
|
compiler_1.identifierName(diDep.token.identifier) == 'TemplateRef')
|
278
|
return true;
|
279
|
}
|
280
|
}
|
281
|
return false;
|
282
|
}
|
283
|
function offsetSpan(span, amount) {
|
284
|
return { start: span.start + amount, end: span.end + amount };
|
285
|
}
|
286
|
function spanOf(sourceSpan) {
|
287
|
return { start: sourceSpan.start.offset, end: sourceSpan.end.offset };
|
288
|
}
|
289
|
//# sourceMappingURL=expression_diagnostics.js.map
|