Change behavior to not override analyzer classes where not necessary.
Work required on the analyzer side to remove `AngularErrorVerifier` and
`AngularResolver`, but this now decouples most of the rest of the behavior
so that removing the former should be simple. Harder will be removing
pipes so that `AngularResolver` can be removed, but that's in the works
too.
diff --git a/angular_analyzer_plugin/lib/src/resolver.dart b/angular_analyzer_plugin/lib/src/resolver.dart
index aa0bac1..3d05268 100644
--- a/angular_analyzer_plugin/lib/src/resolver.dart
+++ b/angular_analyzer_plugin/lib/src/resolver.dart
@@ -33,80 +33,29 @@
return parent is ElementInfo && parent.tagMatchedAsCustomTag;
}
-/// Override the standard [ErrorVerifier] class to report unacceptable nodes,
-/// while suppressing secondary errors that would have been raised by
-/// [ErrorVerifier] if we let it see the bogus definitions.
-class AngularErrorVerifier extends _IntermediateErrorVerifier
- with ReportUnacceptableNodesMixin {
- final bool acceptAssignment;
+/// Overrides standard [ErrorVerifier] to prevent issues with analyzing dangling
+/// angular nodes. Not intended as a long-term solution.
+class AngularErrorVerifier extends ErrorVerifier {
+ AngularErrorVerifier(
+ ErrorReporter errorReporter,
+ LibraryElement currentLibrary,
+ TypeProvider typeProvider,
+ InheritanceManager2 inheritanceManager,
+ {@required bool enableSuperMixins})
+ : super(errorReporter, currentLibrary, typeProvider, inheritanceManager,
+ enableSuperMixins);
@override
- ErrorReporter errorReporter;
- @override
- TypeProvider typeProvider;
-
- AngularErrorVerifier(ErrorReporter errorReporter, LibraryElement library,
- TypeProvider typeProvider, InheritanceManager2 inheritanceManager2,
- {@required this.acceptAssignment})
- : errorReporter = errorReporter,
- typeProvider = typeProvider,
- super(errorReporter, library, typeProvider, inheritanceManager2);
-
- @override
- void visitAssignmentExpression(AssignmentExpression exp) {
- final variableElement = ErrorVerifier.getVariableElement(exp.leftHandSide);
- if ((variableElement == null ||
- variableElement is PropertyInducingElement) &&
- acceptAssignment) {
- super.visitAssignmentExpression(exp);
- } else {
- exp.visitChildren(this);
- }
+ void visitFunctionExpression(FunctionExpression func) {
+ // Stop resolving or analyzer will crash.
+ // TODO(mfairhurst): fix the analyzer crash and remove this.
}
-
- @override
- void visitFunctionExpression(FunctionExpression exp) {
- // error reported in [AngularResolverVisitor] but [ErrorVerifier] crashes
- // because it isn't resolved
- }
-
- @override
- void visitInstanceCreationExpression(InstanceCreationExpression exp) =>
- _reportUnacceptableNode(exp, "Usage of new");
-
- @override
- void visitListLiteral(ListLiteral list) {
- if (list.typeArguments != null) {
- _reportUnacceptableNode(list, "Typed list literals");
- } else {
- super.visitListLiteral(list);
- }
- }
-
- @override
- void visitMapLiteral(MapLiteral map) {
- if (map.typeArguments != null) {
- _reportUnacceptableNode(map, "Typed map literals");
- } else {
- super.visitMapLiteral(map);
- }
- }
-
- @override
- void visitRethrowExpression(RethrowExpression exp) =>
- _reportUnacceptableNode(exp, "Rethrow");
-
- @override
- void visitThisExpression(ThisExpression exp) =>
- _reportUnacceptableNode(exp, "This references");
}
-/// Override the standard [ResolverVisitor] class to report unacceptable nodes,
-/// while suppressing secondary errors that would have been raised by
-/// [ResolverVisitor] if we let it see the bogus definitions.
-class AngularResolverVisitor extends _IntermediateResolverVisitor
- with ReportUnacceptableNodesMixin {
- final bool acceptAssignment;
+/// Overrides standard [ResolverVisitor] to prevent issues with analyzing
+/// dangling angular nodes, while also allowing custom resolution of pipes. Not
+/// intended as a long-term solution.
+class AngularResolverVisitor extends _IntermediateResolverVisitor {
final List<Pipe> pipes;
AngularResolverVisitor(
@@ -115,14 +64,14 @@
Source source,
TypeProvider typeProvider,
AnalysisErrorListener errorListener,
- {@required this.acceptAssignment,
- @required this.pipes})
+ {@required this.pipes})
: super(
inheritanceManager2, library, source, typeProvider, errorListener);
@override
void visitAsExpression(AsExpression exp) {
// This means we generated this in a pipe, and its OK.
+ // TODO(mfairhurst): figure out an alternative approach to this.
if (exp.asOperator.offset == 0) {
super.visitAsExpression(exp);
final pipeName = exp.getProperty<SimpleIdentifier>('_ng_pipeName');
@@ -146,75 +95,14 @@
[exp.expression.staticType, matchingPipe.requiredArgumentType]);
}
}
- } else {
- _reportUnacceptableNode(exp, "As expression");
}
}
@override
- void visitAssignmentExpression(AssignmentExpression exp) {
- if (exp.operator.type != TokenType.EQ) {
- _reportUnacceptableNode(exp, 'Compound assignment');
- }
- // Only block reassignment of locals, not poperties. Resolve elements to
- // check that.
- exp.leftHandSide.accept(elementResolver);
- final variableElement = ErrorVerifier.getVariableElement(exp.leftHandSide);
- if ((variableElement == null ||
- variableElement is PropertyInducingElement) &&
- acceptAssignment) {
- super.visitAssignmentExpression(exp);
- } else {
- _reportUnacceptableNode(exp, "Assignment of locals");
- }
+ void visitFunctionExpression(FunctionExpression func) {
+ // Stop resolving or analyzer will crash.
+ // TODO(mfairhurst): fix the analyzer crash and remove this.
}
-
- @override
- void visitAwaitExpression(AwaitExpression exp) =>
- _reportUnacceptableNode(exp, "Await");
-
- @override
- void visitCascadeExpression(CascadeExpression exp) {
- _reportUnacceptableNode(exp, "Cascades", false);
- // Only resolve the target, not the cascade sections.
- exp.target.accept(this);
- }
-
- @override
- void visitFunctionExpression(FunctionExpression exp) =>
- _reportUnacceptableNode(exp, "Anonymous functions", false);
-
- @override
- void visitIsExpression(IsExpression exp) =>
- _reportUnacceptableNode(exp, "Is expression");
-
- @override
- void visitNamedExpression(NamedExpression exp) =>
- _reportUnacceptableNode(exp, "Named arguments");
-
- @override
- void visitPostfixExpression(PostfixExpression exp) {
- _reportUnacceptableNode(exp, exp.operator.lexeme);
- }
-
- @override
- void visitPrefixExpression(PrefixExpression exp) {
- if (exp.operator.type != TokenType.MINUS) {
- _reportUnacceptableNode(exp, exp.operator.lexeme);
- }
- }
-
- @override
- void visitSuperExpression(SuperExpression exp) =>
- _reportUnacceptableNode(exp, "Super references");
-
- @override
- void visitSymbolLiteral(SymbolLiteral exp) =>
- _reportUnacceptableNode(exp, "Symbol literal");
-
- @override
- void visitThrowExpression(ThrowExpression exp) =>
- _reportUnacceptableNode(exp, "Throw");
}
/// Probably the most important visitor to understand in how we process angular
@@ -311,6 +199,119 @@
}
}
+/// Find nodes which are not supported in angular, such as compound assignment
+/// and function expressions etc.
+class AngularSubsetVisitor extends RecursiveAstVisitor<Object> {
+ final bool acceptAssignment;
+
+ final ErrorReporter errorReporter;
+
+ AngularSubsetVisitor(
+ {@required this.errorReporter, @required this.acceptAssignment});
+
+ @override
+ void visitAsExpression(AsExpression exp) {
+ if (exp.asOperator.offset == 0) {
+ // This means we generated this in a pipe, and its OK.
+ } else {
+ _reportDisallowedExpression(exp, "As expression", visitChildren: false);
+ }
+
+ super.visitAsExpression(exp);
+ }
+
+ @override
+ void visitAssignmentExpression(AssignmentExpression exp) {
+ if (exp.operator.type != TokenType.EQ) {
+ _reportDisallowedExpression(exp, 'Compound assignment',
+ visitChildren: false);
+ }
+ // Only block reassignment of locals, not poperties. Resolve elements to
+ // check that.
+ final variableElement = ErrorVerifier.getVariableElement(exp.leftHandSide);
+ final isLocal =
+ variableElement != null && variableElement is! PropertyInducingElement;
+ if (!acceptAssignment || isLocal) {
+ _reportDisallowedExpression(exp, 'Assignment of locals',
+ visitChildren: false);
+ }
+
+ super.visitAssignmentExpression(exp);
+ }
+
+ @override
+ void visitAwaitExpression(AwaitExpression exp) =>
+ _reportDisallowedExpression(exp, "Await");
+
+ @override
+ void visitCascadeExpression(CascadeExpression exp) =>
+ _reportDisallowedExpression(exp, "Cascades");
+
+ @override
+ void visitFunctionExpression(FunctionExpression exp) =>
+ _reportDisallowedExpression(exp, "Anonymous functions");
+
+ @override
+ void visitInstanceCreationExpression(InstanceCreationExpression exp) =>
+ _reportDisallowedExpression(exp, "Usage of new");
+
+ @override
+ void visitIsExpression(IsExpression exp) =>
+ _reportDisallowedExpression(exp, "Is expression");
+
+ @override
+ void visitListLiteral(ListLiteral list) {
+ if (list.typeArguments != null) {
+ _reportDisallowedExpression(list, "Typed list literals");
+ } else {
+ super.visitListLiteral(list);
+ }
+ }
+
+ @override
+ void visitMapLiteral(MapLiteral map) {
+ if (map.typeArguments != null) {
+ _reportDisallowedExpression(map, "Typed map literals");
+ } else {
+ super.visitMapLiteral(map);
+ }
+ }
+
+ @override
+ void visitNamedExpression(NamedExpression exp) =>
+ _reportDisallowedExpression(exp, "Named arguments");
+
+ @override
+ void visitPostfixExpression(PostfixExpression exp) {
+ _reportDisallowedExpression(exp, exp.operator.lexeme);
+ }
+
+ @override
+ void visitPrefixExpression(PrefixExpression exp) {
+ if (exp.operator.type != TokenType.MINUS) {
+ _reportDisallowedExpression(exp, exp.operator.lexeme);
+ }
+ }
+
+ @override
+ void visitSymbolLiteral(SymbolLiteral exp) =>
+ _reportDisallowedExpression(exp, "Symbol literal");
+
+ @override
+ void visitThrowExpression(ThrowExpression exp) =>
+ _reportDisallowedExpression(exp, "Throw");
+
+ void _reportDisallowedExpression(Expression node, String description,
+ {bool visitChildren = true}) {
+ errorReporter.reportErrorForNode(
+ AngularWarningCode.DISALLOWED_EXPRESSION, node, [description]);
+
+ if (visitChildren) {
+ node.visitChildren(this);
+ }
+ }
+}
+
class ComponentContentResolver extends AngularAstVisitor {
final Source templateSource;
final Template template;
@@ -1187,23 +1188,6 @@
type.lookUpInheritedGetter(name)?.returnType;
}
-abstract class ReportUnacceptableNodesMixin
- implements RecursiveAstVisitor<Object> {
- ErrorReporter get errorReporter;
- TypeProvider get typeProvider;
- void _reportUnacceptableNode(Expression node, String description,
- [bool visitChildren = true]) {
- errorReporter.reportErrorForNode(
- AngularWarningCode.DISALLOWED_EXPRESSION, node, [description]);
-
- // "resolve" the node, a null type causes later errors.
- node.propagatedType = node.staticType = typeProvider.dynamicType;
- if (visitChildren) {
- node.visitChildren(this);
- }
- }
-}
-
/// Once all the scopes for all the expressions & statements are prepared, we're
/// ready to resolve all the expressions inside and typecheck everything.
///
@@ -1565,7 +1549,7 @@
new InheritanceManager2(typeSystem as StrongTypeSystemImpl);
final resolver = new AngularResolverVisitor(inheritanceManager2, library,
templateSource, typeProvider, errorListener,
- acceptAssignment: acceptAssignment, pipes: pipes);
+ pipes: pipes);
// fill the name scope
final classScope = new ClassScope(resolver.nameScope, classElement);
final localScope = new EnclosedScope(classScope);
@@ -1579,9 +1563,13 @@
// verify
final verifier = new AngularErrorVerifier(
errorReporter, library, typeProvider, inheritanceManager2,
- acceptAssignment: acceptAssignment)
+ enableSuperMixins: true)
..enclosingClass = classElement;
astNode.accept(verifier);
+ // Check for concepts illegal to templates (for instance function literals).
+ final angularSubsetChecker = new AngularSubsetVisitor(
+ errorReporter: errorReporter, acceptAssignment: acceptAssignment);
+ astNode.accept(angularSubsetChecker);
}
/// Resolve the Dart expression with the given [code] at [offset].
@@ -1929,20 +1917,6 @@
}
/// Workaround for "This mixin application is invalid because all of the
-/// constructors in the base class 'ErrorVerifier' have optional parameters."
-/// in the definition of [AngularErrorVerifier].
-///
-/// See https://github.com/dart-lang/sdk/issues/15101 for details
-class _IntermediateErrorVerifier extends ErrorVerifier {
- _IntermediateErrorVerifier(
- ErrorReporter errorReporter,
- LibraryElement library,
- TypeProvider typeProvider,
- InheritanceManager2 inheritanceManager2,
- ) : super(errorReporter, library, typeProvider, inheritanceManager2, false);
-}
-
-/// Workaround for "This mixin application is invalid because all of the
/// constructors in the base class 'ResolverVisitor' have optional parameters."
/// in the definition of [AngularResolverVisitor].
///
diff --git a/angular_analyzer_plugin/test/abstract_angular.dart b/angular_analyzer_plugin/test/abstract_angular.dart
index 0901d0e..b9573ce 100644
--- a/angular_analyzer_plugin/test/abstract_angular.dart
+++ b/angular_analyzer_plugin/test/abstract_angular.dart
@@ -116,16 +116,22 @@
AbstractAngularTest() : includeQueryList = true;
AbstractAngularTest.future() : includeQueryList = false;
- /// Assert that the [errCode] is reported for [code], highlighting the [snippet].
+ /// Assert that the [errCode] is reported for [code], highlighting the
+ /// [snippet]. Optionally, expect [additionalErrorCodes] to appear at any
+ /// location.
void assertErrorInCodeAtPosition(
- ErrorCode errCode, String code, String snippet) {
+ ErrorCode errCode, String code, String snippet,
+ {List<ErrorCode> additionalErrorCodes}) {
final snippetIndex = code.indexOf(snippet);
expect(snippetIndex, greaterThan(-1),
reason: 'Error in test: snippet $snippet not part of code $code');
- errorListener.assertErrorsWithCodes(<ErrorCode>[errCode]);
- final error = errorListener.errors.single;
+ final expectedErrorCodes = (additionalErrorCodes ?? <ErrorCode>[])
+ ..add(errCode);
+ errorListener.assertErrorsWithCodes(expectedErrorCodes);
+ final error =
+ errorListener.errors.singleWhere((e) => e.errorCode == errCode);
expect(error.offset, snippetIndex);
- expect(errorListener.errors.single.length, snippet.length);
+ expect(error.length, snippet.length);
}
/// For [expectedErrors], it is a List of Tuple4 (1 per error):
diff --git a/angular_analyzer_plugin/test/resolver_test.dart b/angular_analyzer_plugin/test/resolver_test.dart
index fef6200..8122080 100644
--- a/angular_analyzer_plugin/test/resolver_test.dart
+++ b/angular_analyzer_plugin/test/resolver_test.dart
@@ -1893,7 +1893,10 @@
_addHtmlSource(code);
await _resolveSingleTemplate(dartSource);
assertErrorInCodeAtPosition(
- AngularWarningCode.DISALLOWED_EXPRESSION, code, "#symbol");
+ AngularWarningCode.DISALLOWED_EXPRESSION, code, "#symbol",
+ additionalErrorCodes: [
+ StaticTypeWarningCode.INVOCATION_OF_NON_FUNCTION_EXPRESSION
+ ]);
}
Future
@@ -2147,16 +2150,16 @@
_addDartSource(r'''
@Component(selector: 'test-panel', templateUrl: 'test_panel.html')
class TestPanel {
- String str;
+ bool val;
}
''');
final code = r"""
-<h1 [hidden]="str..x"></h1>
+<h1 [hidden]="val..toString"></h1>
""";
_addHtmlSource(code);
await _resolveSingleTemplate(dartSource);
assertErrorInCodeAtPosition(
- AngularWarningCode.DISALLOWED_EXPRESSION, code, "str..x");
+ AngularWarningCode.DISALLOWED_EXPRESSION, code, "val..toString");
}
// ignore: non_constant_identifier_names
@@ -2270,12 +2273,12 @@
}
''');
final code = r"""
-<h1 [hidden]="new String().isEmpty"></h1>
+<h1 [hidden]="new TestPanel() != null"></h1>
""";
_addHtmlSource(code);
await _resolveSingleTemplate(dartSource);
assertErrorInCodeAtPosition(
- AngularWarningCode.DISALLOWED_EXPRESSION, code, "new String()");
+ AngularWarningCode.DISALLOWED_EXPRESSION, code, "new TestPanel()");
}
Future test_expressionNotAllowed_plusEq() async {
@@ -2370,12 +2373,12 @@
}
''');
final code = r"""
-<h1 #h1 [hidden]="h1 = 4"></h1>
+ <h1 #h1 [hidden]="(h1 = null) == null"></h1>
""";
_addHtmlSource(code);
await _resolveSingleTemplate(dartSource);
assertErrorInCodeAtPosition(
- AngularWarningCode.DISALLOWED_EXPRESSION, code, "h1 = 4");
+ AngularWarningCode.DISALLOWED_EXPRESSION, code, "h1 = null");
}
// ignore: non_constant_identifier_names
@@ -2392,7 +2395,7 @@
_addHtmlSource(code);
await _resolveSingleTemplate(dartSource);
assertErrorInCodeAtPosition(
- AngularWarningCode.DISALLOWED_EXPRESSION, code, "rethrow");
+ CompileTimeErrorCode.RETHROW_OUTSIDE_CATCH, code, "rethrow");
}
// ignore: non_constant_identifier_names
@@ -2404,7 +2407,7 @@
}
''');
final code = r"""
-<h1 [hidden]="str = 'hey'"></h1>
+<h1 [hidden]="(str = 'hey') == null"></h1>
""";
_addHtmlSource(code);
await _resolveSingleTemplate(dartSource);
@@ -2421,12 +2424,12 @@
}
''');
final code = r"""
-<h1 #h1 (click)="h1 = 4"></h1>
+<h1 #h1 (click)="h1 = null"></h1>
""";
_addHtmlSource(code);
await _resolveSingleTemplate(dartSource);
assertErrorInCodeAtPosition(
- AngularWarningCode.DISALLOWED_EXPRESSION, code, "h1 = 4");
+ AngularWarningCode.DISALLOWED_EXPRESSION, code, "h1 = null");
}
// ignore: non_constant_identifier_names
@@ -2443,7 +2446,7 @@
_addHtmlSource(code);
await _resolveSingleTemplate(dartSource);
assertErrorInCodeAtPosition(
- AngularWarningCode.DISALLOWED_EXPRESSION, code, "super");
+ CompileTimeErrorCode.SUPER_IN_INVALID_CONTEXT, code, "super");
}
// ignore: non_constant_identifier_names
@@ -2455,7 +2458,7 @@
}
''');
final code = r"""
-<h1 [hidden]="#symbol"></h1>
+<h1 [hidden]="#symbol == null"></h1>
""";
_addHtmlSource(code);
await _resolveSingleTemplate(dartSource);
@@ -2477,7 +2480,7 @@
_addHtmlSource(code);
await _resolveSingleTemplate(dartSource);
assertErrorInCodeAtPosition(
- AngularWarningCode.DISALLOWED_EXPRESSION, code, "this");
+ CompileTimeErrorCode.INVALID_REFERENCE_TO_THIS, code, "this");
}
// ignore: non_constant_identifier_names