From 41458dc2b26458e4c7389301c460cb31c5d0f093 Mon Sep 17 00:00:00 2001 From: Max Kim Date: Wed, 30 Nov 2016 10:41:29 -0800 Subject: [PATCH 01/41] Sync to origin master --- analyzer_plugin/lib/src/resolver.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/analyzer_plugin/lib/src/resolver.dart b/analyzer_plugin/lib/src/resolver.dart index 9fd6be28..3380c19f 100644 --- a/analyzer_plugin/lib/src/resolver.dart +++ b/analyzer_plugin/lib/src/resolver.dart @@ -715,6 +715,10 @@ class TemplateResolver { } Expression expression = attribute.expression; + + //Check if bound == OUTPUT: + // If so, branch off and deal as statement, + // otherwise, continue stack if (expression == null) { expression = _resolveDartExpressionAt(valueOffset, value, eventType); attribute.expression = expression; From d39f6209950c248550795aaf5ca9131fef5af7da Mon Sep 17 00:00:00 2001 From: Max Kim Date: Fri, 3 Mar 2017 11:26:05 -0800 Subject: [PATCH 02/41] checkpoint --- analyzer_plugin/lib/src/converter.dart | 16 ++++- analyzer_plugin/lib/src/tasks.dart | 86 +++++++++----------------- tools/update_deps.sh | 2 +- 3 files changed, 44 insertions(+), 60 deletions(-) diff --git a/analyzer_plugin/lib/src/converter.dart b/analyzer_plugin/lib/src/converter.dart index 31a3fe16..154f3d40 100644 --- a/analyzer_plugin/lib/src/converter.dart +++ b/analyzer_plugin/lib/src/converter.dart @@ -8,6 +8,7 @@ import 'package:analyzer/src/dart/scanner/reader.dart'; import 'package:analyzer/src/dart/scanner/scanner.dart'; import 'package:analyzer/src/generated/resolver.dart'; import 'package:analyzer/src/generated/source.dart'; +import 'package:angular_ast/angular_ast.dart'; import 'package:angular_analyzer_plugin/ast.dart'; import 'package:angular_analyzer_plugin/src/ng_expr_parser.dart'; import 'package:angular_analyzer_plugin/src/ignoring_error_listener.dart'; @@ -34,7 +35,20 @@ class HtmlTreeConverter { HtmlTreeConverter(this.dartParser, this.templateSource, this.errorListener); - NodeInfo convert(html.Node node, {ElementInfo parent}) { + NodeInfo convert(List documentAsts, {ElementInfo parent}) { + if (documentAsts.length ) + // This is a synthetic 'html' + NodeInfo root = new ElementInfo( + 'html', + null, + null, + null, + null, + false, + [], + null, + null, + ); if (node is html.Element) { String localName = node.localName; List attributes = _convertAttributes(node); diff --git a/analyzer_plugin/lib/src/tasks.dart b/analyzer_plugin/lib/src/tasks.dart index 791527c9..1d36d9fd 100644 --- a/analyzer_plugin/lib/src/tasks.dart +++ b/analyzer_plugin/lib/src/tasks.dart @@ -25,6 +25,7 @@ import 'package:analyzer/src/task/general.dart'; import 'package:analyzer/task/dart.dart'; import 'package:analyzer/task/model.dart'; import 'package:analyzer/task/general.dart'; +import 'package:angular_ast/angular_ast.dart' as NgAst; import 'package:angular_analyzer_plugin/src/from_file_prefixed_error.dart'; import 'package:angular_analyzer_plugin/src/converter.dart'; import 'package:angular_analyzer_plugin/src/model.dart'; @@ -42,15 +43,16 @@ import 'package:source_span/source_span.dart'; /** * The [html.Document] of an HTML file. */ -final ResultDescriptor ANGULAR_HTML_DOCUMENT = - new ResultDescriptor('ANGULAR_HTML_DOCUMENT', null); +final ListResultDescriptor ANGULAR_HTML_DOCUMENT = + new ListResultDescriptor( + 'ANGULAR_HTML_DOCUMENT', []); /** * The analysis errors associated with a [Source] representing an HTML file. */ -final ListResultDescriptor ANGULAR_HTML_DOCUMENT_ERRORS = - new ListResultDescriptor( - 'ANGULAR_HTML_DOCUMENT_ERRORS', AnalysisError.NO_ERRORS); +final ListResultDescriptor ANGULAR_HTML_DOCUMENT_ERRORS = + new ListResultDescriptor( + 'ANGULAR_HTML_DOCUMENT_ERRORS', []); /** * The [Template]s of a [LibrarySpecificUnit]. @@ -295,8 +297,8 @@ class AngularParseHtmlTask extends SourceBasedAnalysisTask with ParseHtmlMixin { target.source, 0, 0, ScannerErrorCode.UNABLE_GET_CONTENT, [message]) ]; } else { - parse(content); - outputs[ANGULAR_HTML_DOCUMENT] = document; + parse(content, target.source.uri.toString()); + outputs[ANGULAR_HTML_DOCUMENT] = documentAsts; outputs[ANGULAR_HTML_DOCUMENT_ERRORS] = parseErrors; } } @@ -315,32 +317,20 @@ class AngularParseHtmlTask extends SourceBasedAnalysisTask with ParseHtmlMixin { } abstract class ParseHtmlMixin implements AnalysisTask { - html.Document document; - final List parseErrors = []; - - void parse(String content) { - AngularHtmlParser parser = new AngularHtmlParser(content, - generateSpans: true, lowercaseAttrName: false); - parser.compatMode = 'quirks'; - document = parser.parse(); - - List htmlErrors = parser.errors; - - for (html.ParseError parseError in htmlErrors) { - if (parseError.errorCode == 'expected-doctype-but-got-start-tag' || - parseError.errorCode == 'expected-doctype-but-got-chars' || - parseError.errorCode == 'expected-doctype-but-got-eof') { - continue; - } + List documentAsts; + final List parseErrors = []; - SourceSpan span = parseError.span; - // html parser lib isn't nice enough to send this error all the time - // see github #47 for dart-lang/html - if (span == null) continue; + void parse(String content, String sourceUrl) { + NgAst.RecoveringExceptionHandler exceptionHandler = + new NgAst.RecoveringExceptionHandler(); + List documentAsts = NgAst.parse( + content, + sourceUrl: sourceUrl, + desugar: false, + exceptionHandler: exceptionHandler, + ); - this.parseErrors.add(new AnalysisError(target.source, span.start.offset, - span.length, HtmlErrorCode.PARSE_ERROR, [parseError.errorCode])); - } + parseErrors.addAll(exceptionHandler.exceptions); } } @@ -1567,12 +1557,13 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask return; } - parse((' ' * view.templateOffset) + - view.templateText.substring(0, view.templateText.length - 1)); - parseErrors.forEach(errorListener.onError); + parse( + (' ' * view.templateOffset) + + view.templateText.substring(0, view.templateText.length - 1), + view.templateUriSource.toString()); parseErrors.clear(); - _processView(new Template(view), document, errorListener, + _processView(new Template(view), documentAsts, errorListener, errorReporter, asts, errorsByFile); } } @@ -1583,7 +1574,7 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask _processView( Template template, - html.Document document, + List documentAsts, RecordingErrorListener errorListener, ErrorReporter errorReporter, List asts, @@ -1595,8 +1586,7 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask template.view.template = template; template.ast = new HtmlTreeConverter(parser, source, errorListener) - .convert(firstElement(document)); - _setIgnoredErrors(template, document); + .convert(documentAsts); template.ast.accept(new NgContentRecorder(template, errorReporter)); @@ -2237,25 +2227,5 @@ class _BuildStandardHtmlComponentsVisitor extends RecursiveAstVisitor { } } -List filterParserErrors( - AngularHtmlParser parser, String content, Source source) { - List errors = []; - List parseErrors = parser.errors; - - for (html.ParseError parseError in parseErrors) { - //Append error codes that are useful to this analyzer - if (parseError.errorCode == 'eof-in-tag-name') { - SourceSpan span = parseError.span; - errors.add(new AnalysisError( - source, - span.start.offset, - span.length, - HtmlErrorCode.PARSE_ERROR, - [parseError.errorCode, content.substring(span.start.offset)])); - } - } - return errors; -} - typedef void CaptureAspectFn( Map aspectMap, PropertyAccessorElement accessor); diff --git a/tools/update_deps.sh b/tools/update_deps.sh index 2e3b7c35..a3415d62 100755 --- a/tools/update_deps.sh +++ b/tools/update_deps.sh @@ -5,7 +5,7 @@ echo done echo echo Updating the sdk with depot_tools -gclient sync +#gclient sync echo done echo From bea4a906669bbb4d8dca3efafd0237b3ee59ba72 Mon Sep 17 00:00:00 2001 From: Max Kim Date: Fri, 3 Mar 2017 16:07:22 -0800 Subject: [PATCH 03/41] Initial ast integration - ranges not behaving properly --- analyzer_plugin/lib/src/converter.dart | 483 ++++++++++++++++--------- analyzer_plugin/lib/src/tasks.dart | 60 ++- analyzer_plugin/test/tasks_test.dart | 59 +-- deps/pubspec.yaml | 4 + 4 files changed, 384 insertions(+), 222 deletions(-) diff --git a/analyzer_plugin/lib/src/converter.dart b/analyzer_plugin/lib/src/converter.dart index 154f3d40..f17d8f71 100644 --- a/analyzer_plugin/lib/src/converter.dart +++ b/analyzer_plugin/lib/src/converter.dart @@ -12,12 +12,10 @@ import 'package:angular_ast/angular_ast.dart'; import 'package:angular_analyzer_plugin/ast.dart'; import 'package:angular_analyzer_plugin/src/ng_expr_parser.dart'; import 'package:angular_analyzer_plugin/src/ignoring_error_listener.dart'; -import 'package:angular_analyzer_plugin/src/angular_html_parser.dart'; import 'package:angular_analyzer_plugin/src/strings.dart'; import 'package:angular_analyzer_plugin/tasks.dart'; import 'package:html/dom.dart' as html; import 'package:html/parser.dart' as html; -import 'package:source_span/source_span.dart'; html.Element firstElement(html.Node node) { for (html.Element child in node.children) { @@ -35,26 +33,53 @@ class HtmlTreeConverter { HtmlTreeConverter(this.dartParser, this.templateSource, this.errorListener); - NodeInfo convert(List documentAsts, {ElementInfo parent}) { - if (documentAsts.length ) - // This is a synthetic 'html' - NodeInfo root = new ElementInfo( - 'html', - null, - null, - null, - null, - false, - [], - null, - null, - ); - if (node is html.Element) { - String localName = node.localName; + NodeInfo convertFromAstList(List asts) { + NodeInfo root; + if (asts.length == 1 && (asts[0] as ElementAst).name == 'html') { + root = convert(asts[0]); + return root; + } else { + root = new ElementInfo( + 'html', + null, + null, + null, + null, + false, + [], + null, + null, + ); + } + for (StandaloneTemplateAst node in asts) { + convert(node, parent: root); + } + return root; + } + + NodeInfo convert(StandaloneTemplateAst node, {ElementInfo parent}) { + // TODO: Handle EmbeddedContentAst case separately + if (node is ElementAst) { + String localName = node.name; List attributes = _convertAttributes(node); bool isTemplate = localName == 'template'; - SourceRange openingSpan = _toSourceRange(node.sourceSpan); - SourceRange closingSpan = _toSourceRange(node.endSourceSpan); + TemplateAst closeComponent = node.closeComplement; + SourceRange openingSpan; + SourceRange closingSpan; + + if (node.isSynthetic) { + openingSpan = _toSourceRange(closeComponent.beginToken.offset, 0); + } else { + openingSpan = _toSourceRange( + node.beginToken.offset, node.endToken.end - node.beginToken.offset); + } + if (closeComponent.isSynthetic) { + closingSpan = _toSourceRange(node.endToken.end, 0); + } else { + closingSpan = _toSourceRange(closeComponent.beginToken.offset, + closeComponent.endToken.end - closeComponent.beginToken.offset); + } + SourceRange openingNameSpan = openingSpan != null ? new SourceRange(openingSpan.offset + '<'.length, localName.length) : null; @@ -90,98 +115,189 @@ class HtmlTreeConverter { return element; } - if (node is html.Text) { + if (node is TextAst) { + int offset = node.sourceSpan.start.offset; + String text = node.value; + return new TextInfo( + offset, text, parent, dartParser.findMustaches(text, offset)); + } + if (node is InterpolationAst) { int offset = node.sourceSpan.start.offset; - String text = node.text; + String text = "{{" + node.value + "}}"; return new TextInfo( offset, text, parent, dartParser.findMustaches(text, offset)); } return null; } - List _convertAttributes(html.Element element) { + List _convertAttributes(ElementAst element) { List attributes = []; - element.attributes.forEach((name, String value) { - if (name is String) { - try { - if (name == "") { - attributes.add(_convertSyntheticAttribute(element)); - } else if (name.startsWith('*')) { - attributes.add(_convertTemplateAttribute(element, name, true)); - } else if (name == 'template') { - attributes.add(_convertTemplateAttribute(element, name, false)); - } else if (name.startsWith('[(')) { - attributes.add(_convertExpressionBoundAttribute( - element, name, "[(", ")]", ExpressionBoundType.twoWay)); - } else if (name.startsWith('[class.')) { - attributes.add(_convertExpressionBoundAttribute( - element, name, "[class.", "]", ExpressionBoundType.clazz)); - } else if (name.startsWith('[attr.')) { - attributes.add(_convertExpressionBoundAttribute( - element, name, "[attr.", "]", ExpressionBoundType.attr)); - } else if (name.startsWith('[style.')) { - attributes.add(_convertExpressionBoundAttribute( - element, name, "[style.", "]", ExpressionBoundType.style)); - } else if (name.startsWith('[')) { - attributes.add(_convertExpressionBoundAttribute( - element, name, "[", "]", ExpressionBoundType.input)); - } else if (name.startsWith('bind-')) { - attributes.add(_convertExpressionBoundAttribute( - element, name, "bind-", null, ExpressionBoundType.input)); - } else if (name.startsWith('on-')) { - attributes.add( - _convertStatementsBoundAttribute(element, name, "on-", null)); - } else if (name.startsWith('(')) { - attributes - .add(_convertStatementsBoundAttribute(element, name, "(", ")")); - } else { - var valueOffset = _valueOffset(element, name); - if (valueOffset == null) { - value = null; - } - - attributes.add(new TextAttribute( - name, - _nameOffset(element, name), - value, - valueOffset, - dartParser.findMustaches(value, valueOffset))); - } - } on IgnorableHtmlInternalError { - // See https://github.com/dart-lang/html/issues/44, this error will - // be thrown looking for nameOffset. Catch it so that analysis else - // where continues. - return; + + // Atttribute/event/properties/etc. within + // [ElementAst] cannot be synthetic as long as Desugaring never occurs. + if (element is ElementAst) { + element.attributes.forEach((AttributeAst attribute) { + if (attribute.name.startsWith('on-')) { + attributes + .add(_convertStatementsBoundAttribute(attribute, "on-", null)); + } else if (attribute.name.startsWith('bind-')) { + attributes.add(_convertExpressionBoundAttribute( + attribute, "bind-", null, ExpressionBoundType.input)); + } else if (attribute.name == 'template') { + attributes.add(_convertTemplateAttribute(attribute)); + } else { + ParsedAttributeAst _attr = attribute as ParsedAttributeAst; + String value = _attr.valueToken.innerValue.lexeme; + int valueOffset = _attr.valueToken.innerValue.offset; + attributes.add(new TextAttribute(_attr.name, _attr.nameOffset, value, + valueOffset, dartParser.findMustaches(value, valueOffset))); } - } - }); + }); + + element.events.forEach((event) { + attributes.add(_convertStatementsBoundAttribute(event, "(", ")")); + }); + + element.bananas.forEach((banana) { + attributes.add(_convertExpressionBoundAttribute( + banana, "[(", ")]", ExpressionBoundType.twoWay)); + }); + + element.properties.forEach((property) { + if (property.name == "class") { + attributes.add(_convertExpressionBoundAttribute( + property, "[class.", "]", ExpressionBoundType.clazz)); + } else if (property.name == "attr") { + attributes.add(_convertExpressionBoundAttribute( + property, "[attr.", "]", ExpressionBoundType.attr)); + } else if (property.name == "style") { + attributes.add(_convertExpressionBoundAttribute( + property, "[style.", "]", ExpressionBoundType.style)); + } else { + attributes.add(_convertExpressionBoundAttribute( + property, "[", "]", ExpressionBoundType.input)); + } + }); + + element.references.forEach((reference) { + ParsedReferenceAst _attr = reference as ParsedReferenceAst; + String value = _attr.valueToken.innerValue.lexeme; + int valueOffset = _attr.valueToken.innerValue.offset; + attributes.add(new TextAttribute( + _attr.prefixToken.lexeme + _attr.nameToken.lexeme, + _attr.prefixToken.offset, + value, + valueOffset, + dartParser.findMustaches(value, valueOffset))); + }); + + element.stars.forEach((star) { + attributes.add(_convertTemplateAttribute(star)); + }); + } +// element.attributes.forEach((name, String value) { +// if (name is String) { +// try { +// if (name == "") { +// attributes.add(_convertSyntheticAttribute(element)); +// } else if (name.startsWith('*')) { +// attributes.add(_convertTemplateAttribute(element, name, true)); +// } else if (name == 'template') { +// attributes.add(_convertTemplateAttribute(element, name, false)); +// } else if (name.startsWith('[(')) { +// attributes.add(_convertExpressionBoundAttribute( +// element, name, "[(", ")]", ExpressionBoundType.twoWay)); +// } else if (name.startsWith('[class.')) { +// attributes.add(_convertExpressionBoundAttribute( +// element, name, "[class.", "]", ExpressionBoundType.clazz)); +// } else if (name.startsWith('[attr.')) { +// attributes.add(_convertExpressionBoundAttribute( +// element, name, "[attr.", "]", ExpressionBoundType.attr)); +// } else if (name.startsWith('[style.')) { +// attributes.add(_convertExpressionBoundAttribute( +// element, name, "[style.", "]", ExpressionBoundType.style)); +// } else if (name.startsWith('[')) { +// attributes.add(_convertExpressionBoundAttribute( +// element, name, "[", "]", ExpressionBoundType.input)); +// } else if (name.startsWith('bind-')) { +// attributes.add(_convertExpressionBoundAttribute( +// element, name, "bind-", null, ExpressionBoundType.input)); +// } else if (name.startsWith('on-')) { +// attributes.add( +// _convertStatementsBoundAttribute(element, name, "on-", null)); +// } else if (name.startsWith('(')) { +// attributes +// .add(_convertStatementsBoundAttribute(element, name, "(", ")")); +// } else { +// var valueOffset = _valueOffset(element, name); +// if (valueOffset == null) { +// value = null; +// } +// +// attributes.add(new TextAttribute( +// name, +// _nameOffset(element, name), +// value, +// valueOffset, +// dartParser.findMustaches(value, valueOffset))); +// } +// } on IgnorableHtmlInternalError { +// // See https://github.com/dart-lang/html/issues/44, this error will +// // be thrown looking for nameOffset. Catch it so that analysis else +// // where continues. +// return; +// } +// } +// }); return attributes; } - TextAttribute _convertSyntheticAttribute(html.Element element) { - FileSpan openSourceSpan = element.sourceSpan; - int nameOffset = openSourceSpan.start.offset + openSourceSpan.length; - TextAttribute textAttribute = - new TextAttribute("", nameOffset, null, null, []); - return textAttribute; - } - - TemplateAttribute _convertTemplateAttribute( - html.Element element, String origName, bool starSugar) { - int origNameOffset = _nameOffset(element, origName); - int valueOffset = _valueOffset(element, origName); - String value = valueOffset == null ? null : element.attributes[origName]; + TemplateAttribute _convertTemplateAttribute(TemplateAst ast) { String name; int nameOffset; + + String value; + int valueOffset; + + String origName; + int origNameOffset; + List virtualAttributes; - if (starSugar) { - nameOffset = origNameOffset + '*'.length; - name = _removePrefixSuffix(origName, '*', null); - virtualAttributes = dartParser.parseTemplateVirtualAttributes( - nameOffset, name + (' ' * '="'.length) + (value ?? '')); - } else { + + if (ast is ParsedStarAst) { + value = ast.value; + valueOffset = ast.valueOffset; + + origName = ast.prefixToken.lexeme + ast.nameToken.lexeme; + origNameOffset = ast.prefixToken.offset; + + name = ast.nameToken.lexeme; + nameOffset = ast.nameToken.offset; + + String fullAstName; + if (value != null) { + fullAstName = ast.name + + (' ' * (ast.equalSignOffset - ast.nameToken.end)) + + ' ' + + (' ' * (ast.valueToken.offset - ast.equalSignToken.end)) + + (value ?? ''); + } else { + fullAstName = ast.name + ' '; + } + + virtualAttributes = + dartParser.parseTemplateVirtualAttributes(nameOffset, fullAstName); + } + if (ast is ParsedAttributeAst) { + value = ast.value; + valueOffset = ast.valueOffset; + + origName = ast.name; + origNameOffset = ast.nameOffset; + name = origName; nameOffset = origNameOffset; + virtualAttributes = dartParser.parseTemplateVirtualAttributes(valueOffset, value); } @@ -203,16 +319,54 @@ class HtmlTreeConverter { } StatementsBoundAttribute _convertStatementsBoundAttribute( - html.Element element, String origName, String prefix, String suffix) { - int origNameOffset = _nameOffset(element, origName); - int valueOffset = _valueOffset(element, origName); - String value = valueOffset == null ? null : element.attributes[origName]; - if (value == null) { - errorListener.onError(new AnalysisError(templateSource, origNameOffset, - origName.length, AngularWarningCode.EMPTY_BINDING, [origName])); + TemplateAst ast, String prefix, String suffix) { + String propName; + int propNameOffset; + + String value; + int valueOffset; + + String origName; + int origNameOffset; + + // TODO: refactor once a generic DecoratorAst is created + if (ast is ParsedAttributeAst) { + origName = ast.name; + origNameOffset = ast.nameOffset; + + value = ast.value; + if (value == null) { + errorListener.onError(new AnalysisError( + templateSource, + origNameOffset, + ast.nameToken.length, + AngularWarningCode.EMPTY_BINDING, + [ast.name])); + } + valueOffset = ast.valueOffset; + + propName = _removePrefixSuffix(origName, prefix, suffix); + propNameOffset = origNameOffset + prefix.length; + } else if (ast is ParsedEventAst) { + origName = ast.prefixToken.lexeme + + ast.nameToken.lexeme + + ast.suffixToken.lexeme; + origNameOffset = ast.prefixToken.offset; + + value = ast.value; + if (value == null) { + errorListener.onError(new AnalysisError( + templateSource, + origNameOffset, + ast.nameToken.length, + AngularWarningCode.EMPTY_BINDING, + [ast.name])); + } + valueOffset = ast.valueOffset; + + propName = _removePrefixSuffix(origName, prefix, suffix); + propNameOffset = origNameOffset + prefix.length; } - int propNameOffset = origNameOffset + prefix.length; - String propName = _removePrefixSuffix(origName, prefix, suffix); return new StatementsBoundAttribute( propName, propNameOffset, @@ -223,24 +377,65 @@ class HtmlTreeConverter { dartParser.parseDartStatements(valueOffset, value)); } - ExpressionBoundAttribute _convertExpressionBoundAttribute( - html.Element element, - String origName, - String prefix, - String suffix, - ExpressionBoundType bound) { - int origNameOffset = _nameOffset(element, origName); - int valueOffset = _valueOffset(element, origName); - String value = valueOffset == null ? null : element.attributes[origName]; - if (value == null || value == "") { - errorListener.onError(new AnalysisError(templateSource, origNameOffset, - origName.length, AngularWarningCode.EMPTY_BINDING, [origName])); - //value = value == "" - // ? "null" - // : value; // we've created a warning. Suppress parse error now. + ExpressionBoundAttribute _convertExpressionBoundAttribute(TemplateAst ast, + String prefix, String suffix, ExpressionBoundType bound) { + String propName; + int propNameOffset; + + String value; + int valueOffset; + + String origName; + int origNameOffset; + + // TODO: Refactor once DecoratorAst is introduced + if (ast is ParsedAttributeAst) { + origName = ast.name; + origNameOffset = ast.nameOffset; + + value = ast.value; + if (value == null || value == "") { + errorListener.onError(new AnalysisError(templateSource, origNameOffset, + origName.length, AngularWarningCode.EMPTY_BINDING, [origName])); + } + valueOffset = ast.valueOffset; + + propName = _removePrefixSuffix(origName, prefix, suffix); + propNameOffset = origNameOffset + prefix.length; } - int propNameOffset = origNameOffset + prefix.length; - String propName = _removePrefixSuffix(origName, prefix, suffix); + if (ast is ParsedEventAst) { + origName = ast.prefixToken.lexeme + + ast.nameToken.lexeme + + ast.suffixToken.lexeme; + origNameOffset = ast.prefixToken.offset; + + value = ast.value; + if (value == null || value == "") { + errorListener.onError(new AnalysisError(templateSource, origNameOffset, + origName.length, AngularWarningCode.EMPTY_BINDING, [origName])); + } + valueOffset = ast.valueOffset; + + propName = _removePrefixSuffix(origName, prefix, suffix); + propNameOffset = origNameOffset + prefix.length; + } + if (ast is ParsedBananaAst) { + origName = ast.prefixToken.lexeme + + ast.nameToken.lexeme + + ast.suffixToken.lexeme; + origNameOffset = ast.prefixToken.offset; + + value = ast.value; + if (value == null || value == "") { + errorListener.onError(new AnalysisError(templateSource, origNameOffset, + origName.length, AngularWarningCode.EMPTY_BINDING, [origName])); + } + valueOffset = ast.valueOffset; + + propName = _removePrefixSuffix(origName, prefix, suffix); + propNameOffset = origNameOffset + prefix.length; + } + return new ExpressionBoundAttribute( propName, propNameOffset, @@ -252,9 +447,10 @@ class HtmlTreeConverter { bound); } - List _convertChildren(html.Element node, ElementInfo parent) { + List _convertChildren( + StandaloneTemplateAst node, ElementInfo parent) { List children = []; - for (html.Node child in node.nodes) { + for (StandaloneTemplateAst child in node.childNodes) { NodeInfo childNode = convert(child, parent: parent); if (childNode != null) { children.add(childNode); @@ -286,45 +482,8 @@ class HtmlTreeConverter { return value; } - int _nameOffset(html.Element element, String name) { - String lowerName = name.toLowerCase(); - try { - return element.attributeSpans[lowerName].start.offset; - // See https://github.com/dart-lang/html/issues/44. - } catch (e) { - try { - AttributeSpanContainer container = - AttributeSpanContainer.generateAttributeSpans(element); - return container.attributeSpans[name].start.offset; - } catch (e) { - throw new IgnorableHtmlInternalError(e); - } - } - } - - int _valueOffset(html.Element element, String name) { - String lowerName = name.toLowerCase(); - try { - SourceSpan span = element.attributeValueSpans[lowerName]; - if (span != null) { - return span.start.offset; - } else { - AttributeSpanContainer container = - AttributeSpanContainer.generateAttributeSpans(element); - return (container.attributeValueSpans.containsKey(name)) - ? container.attributeValueSpans[name].start.offset - : null; - } - } catch (e) { - throw new IgnorableHtmlInternalError(e); - } - } - - SourceRange _toSourceRange(SourceSpan span) { - if (span != null) { - return new SourceRange(span.start.offset, span.length); - } - return null; + SourceRange _toSourceRange(int offset, int length) { + return new SourceRange(offset, length); } } diff --git a/analyzer_plugin/lib/src/tasks.dart b/analyzer_plugin/lib/src/tasks.dart index 1d36d9fd..9e5593c7 100644 --- a/analyzer_plugin/lib/src/tasks.dart +++ b/analyzer_plugin/lib/src/tasks.dart @@ -33,12 +33,8 @@ import 'package:angular_analyzer_plugin/src/resolver.dart'; import 'package:angular_analyzer_plugin/src/selector.dart'; import 'package:angular_analyzer_plugin/tasks.dart'; import 'package:angular_analyzer_plugin/ast.dart'; -import 'package:angular_analyzer_plugin/src/angular_html_parser.dart'; import 'package:front_end/src/scanner/errors.dart'; -import 'package:html/dom.dart' as html; -import 'package:html/parser.dart' as html; import 'package:tuple/tuple.dart'; -import 'package:source_span/source_span.dart'; /** * The [html.Document] of an HTML file. @@ -291,7 +287,7 @@ class AngularParseHtmlTask extends SourceBasedAnalysisTask with ParseHtmlMixin { } } - outputs[ANGULAR_HTML_DOCUMENT] = new html.Document(); + outputs[ANGULAR_HTML_DOCUMENT] = documentAsts; outputs[ANGULAR_HTML_DOCUMENT_ERRORS] = [ new AnalysisError( target.source, 0, 0, ScannerErrorCode.UNABLE_GET_CONTENT, [message]) @@ -323,7 +319,7 @@ abstract class ParseHtmlMixin implements AnalysisTask { void parse(String content, String sourceUrl) { NgAst.RecoveringExceptionHandler exceptionHandler = new NgAst.RecoveringExceptionHandler(); - List documentAsts = NgAst.parse( + documentAsts = NgAst.parse( content, sourceUrl: sourceUrl, desugar: false, @@ -1525,7 +1521,7 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask void internalPerform() { List directives = getRequiredInput(DIRECTIVES_IN_UNIT1_INPUT); - Map documentsMap = + Map> documentsMap = getRequiredInput(HTML_DOCUMENTS_INPUT); Map> documentsErrorsMap = getRequiredInput(HTML_DOCUMENTS_ERRORS_INPUT); @@ -1545,13 +1541,13 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask Source source = view.templateSource; if (view.templateUriSource != null) { - if (documentsMap[source].nodes.length == 0) { + if (documentsMap[source].length == 0) { return; } documentsErrorsMap[source].forEach(errorListener.onError); - _processView(new Template(d.view), documentsMap[source], - errorListener, errorReporter, asts, errorsByFile); + _processView(new Template(d.view), documentAsts, errorListener, + errorReporter, asts, errorsByFile); } else { if (view.templateText == null) { return; @@ -1586,7 +1582,7 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask template.view.template = template; template.ast = new HtmlTreeConverter(parser, source, errorListener) - .convert(documentAsts); + .convertFromAstList(documentAsts); template.ast.accept(new NgContentRecorder(template, errorReporter)); @@ -1597,27 +1593,27 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask } errorsByFile[source].addAll(errorListener.errors); } - - _setIgnoredErrors(Template template, html.Document document) { - if (document == null || document.nodes.length == 0) { - return; - } - html.Node firstNode = document.nodes[0]; - if (firstNode is html.Comment) { - String text = firstNode.text.trim(); - if (text.startsWith("@ngIgnoreErrors")) { - text = text.substring("@ngIgnoreErrors".length); - // Per spec: optional color - if (text.startsWith(":")) { - text = text.substring(1); - } - // Per spec: optional commas - String delim = text.indexOf(',') == -1 ? ' ' : ','; - template.ignoredErrors.addAll(new HashSet.from( - text.split(delim).map((c) => c.trim().toUpperCase()))); - } - } - } +// +// _setIgnoredErrors(Template template, html.Document document) { +// if (document == null || document.nodes.length == 0) { +// return; +// } +// html.Node firstNode = document.nodes[0]; +// if (firstNode is html.Comment) { +// String text = firstNode.text.trim(); +// if (text.startsWith("@ngIgnoreErrors")) { +// text = text.substring("@ngIgnoreErrors".length); +// // Per spec: optional color +// if (text.startsWith(":")) { +// text = text.substring(1); +// } +// // Per spec: optional commas +// String delim = text.indexOf(',') == -1 ? ' ' : ','; +// template.ignoredErrors.addAll(new HashSet.from( +// text.split(delim).map((c) => c.trim().toUpperCase()))); +// } +// } +// } /** * Return a map from the names of the inputs of this kind of task to the diff --git a/analyzer_plugin/test/tasks_test.dart b/analyzer_plugin/test/tasks_test.dart index 801e0615..f04f5248 100644 --- a/analyzer_plugin/test/tasks_test.dart +++ b/analyzer_plugin/test/tasks_test.dart @@ -8,13 +8,13 @@ import 'package:analyzer/src/error/codes.dart'; import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/task/dart.dart'; import 'package:analyzer/task/model.dart'; +import 'package:angular_ast/angular_ast.dart'; import 'package:angular_analyzer_plugin/src/from_file_prefixed_error.dart'; import 'package:angular_analyzer_plugin/src/model.dart'; import 'package:angular_analyzer_plugin/src/selector.dart'; import 'package:angular_analyzer_plugin/src/tasks.dart'; import 'package:angular_analyzer_plugin/tasks.dart'; import 'package:angular_analyzer_plugin/ast.dart'; -import 'package:html/dom.dart' as html; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'package:unittest/unittest.dart'; @@ -76,8 +76,19 @@ class AngularParseHtmlTaskTest extends AbstractAngularTest { } test_perform() { + //TODO: rever back once DOCTYPE is implement +// String code = r''' +// +// +// +// test page +// +// +//

Test

+// +// +// '''; String code = r''' - test page @@ -93,11 +104,14 @@ class AngularParseHtmlTaskTest extends AbstractAngularTest { expect(outputs[ANGULAR_HTML_DOCUMENT_ERRORS], isEmpty); // HTML_DOCUMENT { - html.Document document = outputs[ANGULAR_HTML_DOCUMENT]; - expect(document, isNotNull); + List asts = outputs[ANGULAR_HTML_DOCUMENT]; + expect(asts, isNotNull); + expect(asts.isEmpty, false); // verify that attributes are not lower-cased - html.Element element = document.body.getElementsByTagName('h1').single; - expect(element.attributes['myAttr'], 'my value'); + ElementAst element = asts[0].childNodes[3].childNodes[1]; + expect(element.attributes.length, 1); + expect(element.attributes[0].name, 'myAttr'); + expect(element.attributes[0].value, 'my value'); } } @@ -111,20 +125,11 @@ class AngularParseHtmlTaskTest extends AbstractAngularTest { expect(task, new isInstanceOf()); // validate Document { - html.Document document = outputs[ANGULAR_HTML_DOCUMENT]; - expect(document, isNotNull); - // artificial - expect(document.nodes, hasLength(1)); - html.Element htmlElement = document.nodes[0]; - expect(htmlElement.localName, 'html'); - // artificial - expect(htmlElement.nodes, hasLength(2)); - html.Element bodyElement = htmlElement.nodes[1]; - expect(bodyElement.localName, 'body'); - // actual nodes - expect(bodyElement.nodes, hasLength(4)); - expect((bodyElement.nodes[0] as html.Element).localName, 'div'); - expect((bodyElement.nodes[2] as html.Element).localName, 'span'); + List asts = outputs[ANGULAR_HTML_DOCUMENT]; + expect(asts, isNotNull); + expect(asts.length, 4); + expect((asts[0] as ElementAst).name, 'div'); + expect((asts[2] as ElementAst).name, 'span'); } // it's OK to don't have DOCTYPE expect(outputs[ANGULAR_HTML_DOCUMENT_ERRORS], isEmpty); @@ -140,14 +145,12 @@ class AngularParseHtmlTaskTest extends AbstractAngularTest { expect(task, new isInstanceOf()); // quick validate Document { - html.Document document = outputs[ANGULAR_HTML_DOCUMENT]; - expect(document, isNotNull); - html.Element htmlElement = document.nodes[0]; - html.Element bodyElement = htmlElement.nodes[1]; - expect(bodyElement.nodes, hasLength(5)); - expect((bodyElement.nodes[0] as html.Element).localName, 'div'); - expect((bodyElement.nodes[2] as html.Element).localName, 'span'); - expect((bodyElement.nodes[4] as html.Element).localName, 'di'); + List asts = outputs[ANGULAR_HTML_DOCUMENT]; + expect(asts, isNotNull); + expect(asts.length, 5); + expect((asts[0] as ElementAst).name, 'div'); + expect((asts[2] as ElementAst).name, 'span'); + expect((asts[4] as ElementAst).name, 'di'); } } } diff --git a/deps/pubspec.yaml b/deps/pubspec.yaml index cde74673..17b2272e 100644 --- a/deps/pubspec.yaml +++ b/deps/pubspec.yaml @@ -3,6 +3,10 @@ version: 0.0.0 description: What we need that the sdk doesn't provide dependencies: tuple: '^1.0.1' + angular_ast: + git: + url: https://github.com/mk13/angular_ast + ref: ast_recovery dev_dependencies: test_reflective_loader: '^0.0.3' typed_mock: '^0.0.4' From f8494d124559e4daa0a557a0d8462932249c7709 Mon Sep 17 00:00:00 2001 From: Max Kim Date: Sat, 4 Mar 2017 15:30:51 -0800 Subject: [PATCH 04/41] Minor bug fix --- analyzer_plugin/lib/src/tasks.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/analyzer_plugin/lib/src/tasks.dart b/analyzer_plugin/lib/src/tasks.dart index 9e5593c7..308813c0 100644 --- a/analyzer_plugin/lib/src/tasks.dart +++ b/analyzer_plugin/lib/src/tasks.dart @@ -287,7 +287,7 @@ class AngularParseHtmlTask extends SourceBasedAnalysisTask with ParseHtmlMixin { } } - outputs[ANGULAR_HTML_DOCUMENT] = documentAsts; + outputs[ANGULAR_HTML_DOCUMENT] = []; outputs[ANGULAR_HTML_DOCUMENT_ERRORS] = [ new AnalysisError( target.source, 0, 0, ScannerErrorCode.UNABLE_GET_CONTENT, [message]) @@ -1546,8 +1546,8 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask } documentsErrorsMap[source].forEach(errorListener.onError); - _processView(new Template(d.view), documentAsts, errorListener, - errorReporter, asts, errorsByFile); + _processView(new Template(d.view), documentsMap[source], + errorListener, errorReporter, asts, errorsByFile); } else { if (view.templateText == null) { return; From 4911ffe38a44c64ca69a9702b99ab5a22f5c2fa0 Mon Sep 17 00:00:00 2001 From: Max Kim Date: Mon, 6 Mar 2017 10:58:23 -0800 Subject: [PATCH 05/41] checkpoint --- analyzer_plugin/lib/src/converter.dart | 7 +- analyzer_plugin/lib/src/tasks.dart | 48 ++++----- analyzer_plugin/test/converter_test.dart | 121 +++++++++++++++++++++++ analyzer_plugin/test/tasks_test.dart | 4 +- deps/pubspec.yaml | 1 + 5 files changed, 153 insertions(+), 28 deletions(-) create mode 100644 analyzer_plugin/test/converter_test.dart diff --git a/analyzer_plugin/lib/src/converter.dart b/analyzer_plugin/lib/src/converter.dart index f17d8f71..58b6a05c 100644 --- a/analyzer_plugin/lib/src/converter.dart +++ b/analyzer_plugin/lib/src/converter.dart @@ -33,8 +33,8 @@ class HtmlTreeConverter { HtmlTreeConverter(this.dartParser, this.templateSource, this.errorListener); - NodeInfo convertFromAstList(List asts) { - NodeInfo root; + ElementInfo convertFromAstList(List asts) { + ElementInfo root; if (asts.length == 1 && (asts[0] as ElementAst).name == 'html') { root = convert(asts[0]); return root; @@ -52,7 +52,8 @@ class HtmlTreeConverter { ); } for (StandaloneTemplateAst node in asts) { - convert(node, parent: root); + NodeInfo child = convert(node, parent: root); + root.childNodes.add(child); } return root; } diff --git a/analyzer_plugin/lib/src/tasks.dart b/analyzer_plugin/lib/src/tasks.dart index 308813c0..6d96fc08 100644 --- a/analyzer_plugin/lib/src/tasks.dart +++ b/analyzer_plugin/lib/src/tasks.dart @@ -43,12 +43,13 @@ final ListResultDescriptor ANGULAR_HTML_DOCUMENT = new ListResultDescriptor( 'ANGULAR_HTML_DOCUMENT', []); +//TODO: Max: reimplement once Exception is switched to AnalysisError in ast /** * The analysis errors associated with a [Source] representing an HTML file. */ -final ListResultDescriptor ANGULAR_HTML_DOCUMENT_ERRORS = - new ListResultDescriptor( - 'ANGULAR_HTML_DOCUMENT_ERRORS', []); +//final ListResultDescriptor ANGULAR_HTML_DOCUMENT_ERRORS = +// new ListResultDescriptor( +// 'ANGULAR_HTML_DOCUMENT_ERRORS', []); /** * The [Template]s of a [LibrarySpecificUnit]. @@ -262,7 +263,7 @@ class AngularParseHtmlTask extends SourceBasedAnalysisTask with ParseHtmlMixin { static final TaskDescriptor DESCRIPTOR = new TaskDescriptor( 'AngularParseHtmlTask', createTask, buildInputs, [ ANGULAR_HTML_DOCUMENT, - ANGULAR_HTML_DOCUMENT_ERRORS, + //ANGULAR_HTML_DOCUMENT_ERRORS, ]); AngularParseHtmlTask(InternalAnalysisContext context, AnalysisTarget target) @@ -288,14 +289,14 @@ class AngularParseHtmlTask extends SourceBasedAnalysisTask with ParseHtmlMixin { } outputs[ANGULAR_HTML_DOCUMENT] = []; - outputs[ANGULAR_HTML_DOCUMENT_ERRORS] = [ - new AnalysisError( - target.source, 0, 0, ScannerErrorCode.UNABLE_GET_CONTENT, [message]) - ]; +// outputs[ANGULAR_HTML_DOCUMENT_ERRORS] = [ +// new AnalysisError( +// target.source, 0, 0, ScannerErrorCode.UNABLE_GET_CONTENT, [message]) +// ]; } else { parse(content, target.source.uri.toString()); outputs[ANGULAR_HTML_DOCUMENT] = documentAsts; - outputs[ANGULAR_HTML_DOCUMENT_ERRORS] = parseErrors; + //outputs[ANGULAR_HTML_DOCUMENT_ERRORS] = parseErrors; } } @@ -1501,8 +1502,8 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask with ParseHtmlMixin { static const String DIRECTIVES_IN_UNIT1_INPUT = 'DIRECTIVES_IN_UNIT1_INPUT'; static const String HTML_DOCUMENTS_INPUT = 'HTML_DOCUMENTS_INPUT'; - static const String HTML_DOCUMENTS_ERRORS_INPUT = - 'HTML_DOCUMENTS_ERRORS_INPUT'; + //static const String HTML_DOCUMENTS_ERRORS_INPUT = + // 'HTML_DOCUMENTS_ERRORS_INPUT'; static const String TYPE_PROVIDER_INPUT = 'TYPE_PROVIDER_INPUT'; static const String EXTRA_NODES_INPUT = 'EXTRA_NODES_INPUT'; @@ -1523,8 +1524,8 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask getRequiredInput(DIRECTIVES_IN_UNIT1_INPUT); Map> documentsMap = getRequiredInput(HTML_DOCUMENTS_INPUT); - Map> documentsErrorsMap = - getRequiredInput(HTML_DOCUMENTS_ERRORS_INPUT); +// Map> documentsErrorsMap = +// getRequiredInput(HTML_DOCUMENTS_ERRORS_INPUT); List asts = []; Map> errorsByFile = >{}; @@ -1545,7 +1546,8 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask return; } - documentsErrorsMap[source].forEach(errorListener.onError); + //TODO: Max: Figure out why this keeps throwing errors later + //documentsErrorsMap[source].forEach(errorListener.onError); _processView(new Template(d.view), documentsMap[source], errorListener, errorReporter, asts, errorsByFile); } else { @@ -1633,15 +1635,15 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask .toList()) // to map .toMapOf(ANGULAR_HTML_DOCUMENT), - HTML_DOCUMENTS_ERRORS_INPUT: VIEWS_WITH_HTML_TEMPLATES1 - .of(target) - // mapped to html source of the view - .mappedToList((views) => views - .map((v) => v.templateUriSource) - .where((v) => v != null) - .toList()) - // to map - .toMapOf(ANGULAR_HTML_DOCUMENT_ERRORS), +// HTML_DOCUMENTS_ERRORS_INPUT: VIEWS_WITH_HTML_TEMPLATES1 +// .of(target) +// // mapped to html source of the view +// .mappedToList((views) => views +// .map((v) => v.templateUriSource) +// .where((v) => v != null) +// .toList()) +// // to map +// .toMapOf(ANGULAR_HTML_DOCUMENT_ERRORS), }; } diff --git a/analyzer_plugin/test/converter_test.dart b/analyzer_plugin/test/converter_test.dart new file mode 100644 index 00000000..c19f87ea --- /dev/null +++ b/analyzer_plugin/test/converter_test.dart @@ -0,0 +1,121 @@ +import 'package:analyzer/src/generated/source.dart'; +import 'package:analyzer/task/dart.dart'; +import 'package:angular_analyzer_plugin/ast.dart'; +import 'package:angular_analyzer_plugin/src/model.dart'; +import 'package:angular_analyzer_plugin/src/tasks.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; +import 'package:unittest/unittest.dart'; + +import 'abstract_angular.dart'; +import 'element_assert.dart'; + +main() { + groupSep = ' | '; + defineReflectiveTests(ConverterTest); +} + +@reflectiveTest +class ConverterTest extends AbstractAngularTest { + String dartCode; + String htmlCode; + Source dartSource; + Source htmlSource; + + List directives; + + Template template; + List ranges; + + void test_inline_conversion() { + String code = r''' +import '/angular2/angular2.dart'; + +@Component(selector: 'text-panel', + template: r"
{{text.length + text}}
") +class TextPanel { + String text; +} +'''; + Source source = newSource('/test.dart', code); + LibrarySpecificUnit target = new LibrarySpecificUnit(source, source); + //computeResult(target, DART_TEMPLATES); + computeResult(target, VIEWS1); + final view = outputs[VIEWS1][0]; + print(view.template); + print(view.templateText); + } + + void test_event_attribute() { + _addDartSource(r''' +import 'dart:html'; +@Component(selector: 'test-panel') +@View(templateUrl: 'test_panel.html') +class TestPanel { + void handleClick(MouseEvent e) { + } +} +'''); + _addHtmlSource(r""" +
+"""); + + computeResult( + new LibrarySpecificUnit(dartSource, dartSource), ANGULAR_ASTS); + _resolveSingleTemplate(dartSource); + expect(template.ast, new isInstanceOf()); + ElementInfo root = template.ast; + expect(root.localName, 'html'); + expect(root.childNodes.length, 2); + ElementInfo div = root.childNodes[0]; + expect(div.localName, 'div'); + expect(div.attributes.length, 1); + AttributeInfo event = div.attributes[0]; + expect(event.originalName, '(click)'); + expect(event.name, 'click'); + expect(event.value, 'handleClick(\$event)'); + } + + void test_expression_nativeEventBindingOnComponent() { + _addDartSource(r''' +import 'dart:html'; +@Component(selector: 'test-panel') +@View(templateUrl: 'test_panel.html', directives: [SomeComponent]) +class TestPanel { + void handleClick(MouseEvent e) { + } +} + +@Component(selector: 'some-comp', template: '') +class SomeComponent { +} +'''); + _addHtmlSource(r""" + +"""); + _resolveSingleTemplate(dartSource); + } + + void _addDartSource(String code) { + dartCode = ''' +import '/angular2/angular2.dart'; +$code +'''; + dartSource = newSource('/test_panel.dart', dartCode); + } + + void _addHtmlSource(String code) { + htmlCode = code; + htmlSource = newSource('/test_panel.html', htmlCode); + } + + void _resolveSingleTemplate(Source dartSource) { + directives = computeLibraryDirectives(dartSource); + List views = computeLibraryViews(dartSource); + View view = views.last; + // resolve this View + computeResult(view, HTML_TEMPLATE); + template = outputs[HTML_TEMPLATE]; + ranges = template.ranges; + fillErrorListener(HTML_TEMPLATE_ERRORS); + } +} diff --git a/analyzer_plugin/test/tasks_test.dart b/analyzer_plugin/test/tasks_test.dart index f04f5248..1ba6e42d 100644 --- a/analyzer_plugin/test/tasks_test.dart +++ b/analyzer_plugin/test/tasks_test.dart @@ -101,7 +101,7 @@ class AngularParseHtmlTaskTest extends AbstractAngularTest { AnalysisTarget target = newSource('/test.html', code); computeResult(target, ANGULAR_HTML_DOCUMENT); expect(task, new isInstanceOf()); - expect(outputs[ANGULAR_HTML_DOCUMENT_ERRORS], isEmpty); + //expect(outputs[ANGULAR_HTML_DOCUMENT_ERRORS], isEmpty); // HTML_DOCUMENT { List asts = outputs[ANGULAR_HTML_DOCUMENT]; @@ -132,7 +132,7 @@ class AngularParseHtmlTaskTest extends AbstractAngularTest { expect((asts[2] as ElementAst).name, 'span'); } // it's OK to don't have DOCTYPE - expect(outputs[ANGULAR_HTML_DOCUMENT_ERRORS], isEmpty); + //expect(outputs[ANGULAR_HTML_DOCUMENT_ERRORS], isEmpty); } test_perform_noDocType_with_dangling_unclosed_tag() { diff --git a/deps/pubspec.yaml b/deps/pubspec.yaml index 17b2272e..7b551fc2 100644 --- a/deps/pubspec.yaml +++ b/deps/pubspec.yaml @@ -11,3 +11,4 @@ dev_dependencies: test_reflective_loader: '^0.0.3' typed_mock: '^0.0.4' unittest: '^0.11.0' + test: '^0.12.17' From f1f19a002b2035aa287b810487c9a8deaa525740 Mon Sep 17 00:00:00 2001 From: Max Kim Date: Sat, 11 Mar 2017 16:20:06 -0800 Subject: [PATCH 06/41] Tasks tests all passing --- analyzer_plugin/lib/ast.dart | 1 + analyzer_plugin/lib/src/converter.dart | 169 ++++++++++++----------- analyzer_plugin/lib/src/tasks.dart | 130 +++++++++-------- analyzer_plugin/lib/tasks.dart | 6 + analyzer_plugin/test/converter_test.dart | 167 +++++++++++++--------- analyzer_plugin/test/tasks_test.dart | 18 ++- 6 files changed, 282 insertions(+), 209 deletions(-) diff --git a/analyzer_plugin/lib/ast.dart b/analyzer_plugin/lib/ast.dart index 2277cf6a..7cb8d7cd 100644 --- a/analyzer_plugin/lib/ast.dart +++ b/analyzer_plugin/lib/ast.dart @@ -326,6 +326,7 @@ class ElementInfo extends NodeInfo implements HasDirectives { } } + // TODO: Max: Handle synthetic detection. @override bool get isSynthetic => openingSpan == null; bool get openingSpanIsClosed => isSynthetic diff --git a/analyzer_plugin/lib/src/converter.dart b/analyzer_plugin/lib/src/converter.dart index 58b6a05c..29bd489a 100644 --- a/analyzer_plugin/lib/src/converter.dart +++ b/analyzer_plugin/lib/src/converter.dart @@ -35,16 +35,19 @@ class HtmlTreeConverter { ElementInfo convertFromAstList(List asts) { ElementInfo root; - if (asts.length == 1 && (asts[0] as ElementAst).name == 'html') { + if (asts != null && + asts.length == 1 && + asts[0] is ElementAst && + (asts[0] as ElementAst).name == 'html') { root = convert(asts[0]); return root; } else { root = new ElementInfo( 'html', - null, - null, - null, - null, + new SourceRange(0, 0), + new SourceRange(0, 0), + new SourceRange(0, 0), + new SourceRange(0, 0), false, [], null, @@ -53,40 +56,52 @@ class HtmlTreeConverter { } for (StandaloneTemplateAst node in asts) { NodeInfo child = convert(node, parent: root); - root.childNodes.add(child); + if (child != null) { + root.childNodes.add(child); + } } return root; } NodeInfo convert(StandaloneTemplateAst node, {ElementInfo parent}) { - // TODO: Handle EmbeddedContentAst case separately if (node is ElementAst) { String localName = node.name; List attributes = _convertAttributes(node); bool isTemplate = localName == 'template'; - TemplateAst closeComponent = node.closeComplement; + final closeComponent = node.closeComplement; SourceRange openingSpan; + SourceRange openingNameSpan; SourceRange closingSpan; + SourceRange closingNameSpan; if (node.isSynthetic) { openingSpan = _toSourceRange(closeComponent.beginToken.offset, 0); + openingNameSpan = openingSpan; } else { openingSpan = _toSourceRange( node.beginToken.offset, node.endToken.end - node.beginToken.offset); + openingNameSpan = + new SourceRange(openingSpan.offset + '<'.length, localName.length); } - if (closeComponent.isSynthetic) { - closingSpan = _toSourceRange(node.endToken.end, 0); - } else { - closingSpan = _toSourceRange(closeComponent.beginToken.offset, - closeComponent.endToken.end - closeComponent.beginToken.offset); + // Check for void element cases (has closing complement) + if (closeComponent != null) { + if (closeComponent.isSynthetic) { + closingSpan = _toSourceRange(node.endToken.end, 0); + closingNameSpan = closingSpan; + } else { + closingSpan = _toSourceRange(closeComponent.beginToken.offset, + closeComponent.endToken.end - closeComponent.beginToken.offset); + closingNameSpan = new SourceRange( + closingSpan.offset + '[]; + final closeComplement = node.closeComplement; + SourceRange openingSpan; + SourceRange openingNameSpan; + SourceRange closingSpan; + SourceRange closingNameSpan; + + if (node.isSynthetic) { + openingSpan = _toSourceRange(closeComplement.beginToken.offset, 0); + openingNameSpan = openingSpan; + } else { + openingSpan = _toSourceRange( + node.beginToken.offset, node.endToken.end - node.beginToken.offset); + openingNameSpan = + new SourceRange(openingSpan.offset + '<'.length, localName.length); + var pnode = node as ParsedEmbeddedContentAst; + var valueToken = pnode.selectorValueToken; + if (pnode.selectorValueToken != null) { + attributes.add(new TextAttribute( + 'select', + pnode.identifierToken.offset, + valueToken.lexeme, + valueToken.offset, + [], + )); + } + } + + if (closeComplement.isSynthetic) { + closingSpan = _toSourceRange(node.endToken.end, 0); + closingNameSpan = closingSpan; + } else { + closingSpan = _toSourceRange(closeComplement.beginToken.offset, + closeComplement.endToken.end - closeComplement.beginToken.offset); + closingNameSpan = + new SourceRange(closingSpan.offset + ' ANGULAR_HTML_DOCUMENT = new ListResultDescriptor( 'ANGULAR_HTML_DOCUMENT', []); -//TODO: Max: reimplement once Exception is switched to AnalysisError in ast /** * The analysis errors associated with a [Source] representing an HTML file. */ -//final ListResultDescriptor ANGULAR_HTML_DOCUMENT_ERRORS = -// new ListResultDescriptor( -// 'ANGULAR_HTML_DOCUMENT_ERRORS', []); +final ListResultDescriptor ANGULAR_HTML_DOCUMENT_ERRORS = + new ListResultDescriptor( + 'ANGULAR_HTML_DOCUMENT_ERRORS', []); /** * The [Template]s of a [LibrarySpecificUnit]. @@ -263,7 +262,7 @@ class AngularParseHtmlTask extends SourceBasedAnalysisTask with ParseHtmlMixin { static final TaskDescriptor DESCRIPTOR = new TaskDescriptor( 'AngularParseHtmlTask', createTask, buildInputs, [ ANGULAR_HTML_DOCUMENT, - //ANGULAR_HTML_DOCUMENT_ERRORS, + ANGULAR_HTML_DOCUMENT_ERRORS, ]); AngularParseHtmlTask(InternalAnalysisContext context, AnalysisTarget target) @@ -289,14 +288,14 @@ class AngularParseHtmlTask extends SourceBasedAnalysisTask with ParseHtmlMixin { } outputs[ANGULAR_HTML_DOCUMENT] = []; -// outputs[ANGULAR_HTML_DOCUMENT_ERRORS] = [ -// new AnalysisError( -// target.source, 0, 0, ScannerErrorCode.UNABLE_GET_CONTENT, [message]) -// ]; + outputs[ANGULAR_HTML_DOCUMENT_ERRORS] = [ + new AnalysisError( + target.source, 0, 0, ScannerErrorCode.UNABLE_GET_CONTENT, [message]) + ]; } else { - parse(content, target.source.uri.toString()); + parse(content, target.source.uri.path); outputs[ANGULAR_HTML_DOCUMENT] = documentAsts; - //outputs[ANGULAR_HTML_DOCUMENT_ERRORS] = parseErrors; + outputs[ANGULAR_HTML_DOCUMENT_ERRORS] = parseErrors; } } @@ -315,11 +314,10 @@ class AngularParseHtmlTask extends SourceBasedAnalysisTask with ParseHtmlMixin { abstract class ParseHtmlMixin implements AnalysisTask { List documentAsts; - final List parseErrors = []; + final parseErrors = []; void parse(String content, String sourceUrl) { - NgAst.RecoveringExceptionHandler exceptionHandler = - new NgAst.RecoveringExceptionHandler(); + final exceptionHandler = new NgAst.RecoveringExceptionHandler(); documentAsts = NgAst.parse( content, sourceUrl: sourceUrl, @@ -327,7 +325,23 @@ abstract class ParseHtmlMixin implements AnalysisTask { exceptionHandler: exceptionHandler, ); - parseErrors.addAll(exceptionHandler.exceptions); + for (NgAst.AngularParserException e in exceptionHandler.exceptions) { + ErrorCode errorCode; + if (e.message == 'Unopened mustache') { + errorCode = AngularWarningCode.UNOPENED_MUSTACHE; + } else if (e.message == 'Unclosed mustache') { + errorCode = AngularWarningCode.UNTERMINATED_MUSTACHE; + } else { + errorCode = AngularWarningCode.ANGULAR_PARSER_ERROR; + } + + this.parseErrors.add(new AnalysisError( + target.source, + e.offset, + e.context.length, + errorCode, + )); + } } } @@ -1005,7 +1019,6 @@ class BuildUnitViewsTask extends SourceBasedAnalysisTask //@TODO when there's both a @View and @Component, handle edge cases View view = _createView(classElement, viewAnnotation ?? componentAnnotation); - if (view != null) { views.add(view); if (view.templateUriSource != null) { @@ -1502,8 +1515,8 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask with ParseHtmlMixin { static const String DIRECTIVES_IN_UNIT1_INPUT = 'DIRECTIVES_IN_UNIT1_INPUT'; static const String HTML_DOCUMENTS_INPUT = 'HTML_DOCUMENTS_INPUT'; - //static const String HTML_DOCUMENTS_ERRORS_INPUT = - // 'HTML_DOCUMENTS_ERRORS_INPUT'; + static const String HTML_DOCUMENTS_ERRORS_INPUT = + 'HTML_DOCUMENTS_ERRORS_INPUT'; static const String TYPE_PROVIDER_INPUT = 'TYPE_PROVIDER_INPUT'; static const String EXTRA_NODES_INPUT = 'EXTRA_NODES_INPUT'; @@ -1524,8 +1537,8 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask getRequiredInput(DIRECTIVES_IN_UNIT1_INPUT); Map> documentsMap = getRequiredInput(HTML_DOCUMENTS_INPUT); -// Map> documentsErrorsMap = -// getRequiredInput(HTML_DOCUMENTS_ERRORS_INPUT); + Map> documentsErrorsMap = + getRequiredInput(HTML_DOCUMENTS_ERRORS_INPUT); List asts = []; Map> errorsByFile = >{}; @@ -1539,15 +1552,9 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask RecordingErrorListener errorListener = new RecordingErrorListener(); ErrorReporter errorReporter = new ErrorReporter(errorListener, view.templateSource); - Source source = view.templateSource; if (view.templateUriSource != null) { - if (documentsMap[source].length == 0) { - return; - } - - //TODO: Max: Figure out why this keeps throwing errors later - //documentsErrorsMap[source].forEach(errorListener.onError); + documentsErrorsMap[source].forEach(errorListener.onError); _processView(new Template(d.view), documentsMap[source], errorListener, errorReporter, asts, errorsByFile); } else { @@ -1558,7 +1565,8 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask parse( (' ' * view.templateOffset) + view.templateText.substring(0, view.templateText.length - 1), - view.templateUriSource.toString()); + source.uri.path); + parseErrors.forEach(errorListener.onError); parseErrors.clear(); _processView(new Template(view), documentAsts, errorListener, @@ -1585,6 +1593,7 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask template.ast = new HtmlTreeConverter(parser, source, errorListener) .convertFromAstList(documentAsts); + _setIgnoredErrors(template, documentAsts); template.ast.accept(new NgContentRecorder(template, errorReporter)); @@ -1595,27 +1604,32 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask } errorsByFile[source].addAll(errorListener.errors); } -// -// _setIgnoredErrors(Template template, html.Document document) { -// if (document == null || document.nodes.length == 0) { -// return; -// } -// html.Node firstNode = document.nodes[0]; -// if (firstNode is html.Comment) { -// String text = firstNode.text.trim(); -// if (text.startsWith("@ngIgnoreErrors")) { -// text = text.substring("@ngIgnoreErrors".length); -// // Per spec: optional color -// if (text.startsWith(":")) { -// text = text.substring(1); -// } -// // Per spec: optional commas -// String delim = text.indexOf(',') == -1 ? ' ' : ','; -// template.ignoredErrors.addAll(new HashSet.from( -// text.split(delim).map((c) => c.trim().toUpperCase()))); -// } -// } -// } + + _setIgnoredErrors(Template template, List asts) { + if (asts == null || asts.length == 0) { + return; + } + for (NgAst.TemplateAst ast in asts) { + if (ast is NgAst.TextAst && ast.value.trim().isEmpty) { + continue; + } else if (ast is NgAst.CommentAst) { + String text = ast.value.trim(); + if (text.startsWith("@ngIgnoreErrors")) { + text = text.substring("@ngIgnoreErrors".length); + // Per spec: optional color + if (text.startsWith(":")) { + text = text.substring(1); + } + // Per spec: optional commas + String delim = text.indexOf(',') == -1 ? ' ' : ','; + template.ignoredErrors.addAll(new HashSet.from( + text.split(delim).map((c) => c.trim().toUpperCase()))); + } + } else { + return; + } + } + } /** * Return a map from the names of the inputs of this kind of task to the @@ -1635,15 +1649,15 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask .toList()) // to map .toMapOf(ANGULAR_HTML_DOCUMENT), -// HTML_DOCUMENTS_ERRORS_INPUT: VIEWS_WITH_HTML_TEMPLATES1 -// .of(target) -// // mapped to html source of the view -// .mappedToList((views) => views -// .map((v) => v.templateUriSource) -// .where((v) => v != null) -// .toList()) -// // to map -// .toMapOf(ANGULAR_HTML_DOCUMENT_ERRORS), + HTML_DOCUMENTS_ERRORS_INPUT: VIEWS_WITH_HTML_TEMPLATES1 + .of(target) + // mapped to html source of the view + .mappedToList((views) => views + .map((v) => v.templateUriSource) + .where((v) => v != null) + .toList()) + // to map + .toMapOf(ANGULAR_HTML_DOCUMENT_ERRORS), }; } diff --git a/analyzer_plugin/lib/tasks.dart b/analyzer_plugin/lib/tasks.dart index bc376740..926528bd 100644 --- a/analyzer_plugin/lib/tasks.dart +++ b/analyzer_plugin/lib/tasks.dart @@ -9,6 +9,12 @@ import 'package:analyzer/error/error.dart'; * and, when appropriate, how the problem can be corrected. */ class AngularWarningCode extends ErrorCode { + /** + * An error code indicating that the error originated from Angular AST parse. + */ + static const AngularWarningCode ANGULAR_PARSER_ERROR = + const AngularWarningCode('ANGULAR_PARSER_ERROR', 'Unexpected token'); + /** * An error code indicating that the annotation does not define the * required "selector" argument. diff --git a/analyzer_plugin/test/converter_test.dart b/analyzer_plugin/test/converter_test.dart index c19f87ea..9329dc90 100644 --- a/analyzer_plugin/test/converter_test.dart +++ b/analyzer_plugin/test/converter_test.dart @@ -1,13 +1,12 @@ import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/task/dart.dart'; -import 'package:angular_analyzer_plugin/ast.dart'; import 'package:angular_analyzer_plugin/src/model.dart'; import 'package:angular_analyzer_plugin/src/tasks.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'package:unittest/unittest.dart'; +import 'package:angular_analyzer_plugin/ast.dart'; import 'abstract_angular.dart'; -import 'element_assert.dart'; main() { groupSep = ' | '; @@ -25,74 +24,110 @@ class ConverterTest extends AbstractAngularTest { Template template; List ranges; +// +// void test_inline_conversion() { +// String code = r''' +//import '/angular2/angular2.dart'; +// +//@Component(selector: 'text-panel', +// template: r"
{{text.length + text}}
") +//class TextPanel { +// String text; +//} +//'''; +// Source source = newSource('/test.dart', code); +// LibrarySpecificUnit target = new LibrarySpecificUnit(source, source); +// //computeResult(target, DART_TEMPLATES); +// computeResult(target, VIEWS1); +// final view = outputs[VIEWS1][0]; +// print(view.template); +// print(view.templateText); +// } +// +// void test_event_attribute() { +// _addDartSource(r''' +//import 'dart:html'; +//@Component(selector: 'test-panel') +//@View(templateUrl: 'test_panel.html') +//class TestPanel { +// void handleClick(MouseEvent e) { +// } +//} +//'''); +// _addHtmlSource(r""" +//
+//"""); +// +// computeResult( +// new LibrarySpecificUnit(dartSource, dartSource), ANGULAR_ASTS); +// _resolveSingleTemplate(dartSource); +// expect(template.ast, new isInstanceOf()); +// ElementInfo root = template.ast; +// expect(root.localName, 'html'); +// expect(root.childNodes.length, 2); +// ElementInfo div = root.childNodes[0]; +// expect(div.localName, 'div'); +// expect(div.attributes.length, 1); +// AttributeInfo event = div.attributes[0]; +// expect(event.originalName, '(click)'); +// expect(event.name, 'click'); +// expect(event.value, 'handleClick(\$event)'); +// } +// +// void test_property_attribute() { +// _addDartSource(r''' +//import 'dart:html'; +//@Component(selector: 'test-panel') +//@View(templateUrl: 'test_panel.html') +//class TestPanel { +// void handleClick(MouseEvent e) { +// } +//} +//'''); +// _addHtmlSource(r""" +//
+// +//
+//"""); +// computeResult( +// new LibrarySpecificUnit(dartSource, dartSource), ANGULAR_ASTS); +// _resolveSingleTemplate(dartSource); +// +// } - void test_inline_conversion() { + void test_scratch() { String code = r''' import '/angular2/angular2.dart'; +import 'child_file.dart'; -@Component(selector: 'text-panel', - template: r"
{{text.length + text}}
") -class TextPanel { - String text; -} +import '/angular2/angular2.dart'; +@Component(selector: 'my-component', templateUrl: 'test.html', + directives: const [ChildComponent]) +class MyComponent {} +'''; + String childCode = r''' +import '/angular2/angular2.dart'; +@Component(selector: 'child-component', + template: 'My template ', + directives: const []) +class ChildComponent {} '''; Source source = newSource('/test.dart', code); - LibrarySpecificUnit target = new LibrarySpecificUnit(source, source); - //computeResult(target, DART_TEMPLATES); - computeResult(target, VIEWS1); - final view = outputs[VIEWS1][0]; - print(view.template); - print(view.templateText); - } - - void test_event_attribute() { - _addDartSource(r''' -import 'dart:html'; -@Component(selector: 'test-panel') -@View(templateUrl: 'test_panel.html') -class TestPanel { - void handleClick(MouseEvent e) { - } -} -'''); - _addHtmlSource(r""" -
-"""); - - computeResult( - new LibrarySpecificUnit(dartSource, dartSource), ANGULAR_ASTS); - _resolveSingleTemplate(dartSource); - expect(template.ast, new isInstanceOf()); - ElementInfo root = template.ast; - expect(root.localName, 'html'); - expect(root.childNodes.length, 2); - ElementInfo div = root.childNodes[0]; - expect(div.localName, 'div'); - expect(div.attributes.length, 1); - AttributeInfo event = div.attributes[0]; - expect(event.originalName, '(click)'); - expect(event.name, 'click'); - expect(event.value, 'handleClick(\$event)'); - } - - void test_expression_nativeEventBindingOnComponent() { - _addDartSource(r''' -import 'dart:html'; -@Component(selector: 'test-panel') -@View(templateUrl: 'test_panel.html', directives: [SomeComponent]) -class TestPanel { - void handleClick(MouseEvent e) { - } -} - -@Component(selector: 'some-comp', template: '') -class SomeComponent { -} -'''); - _addHtmlSource(r""" - -"""); - _resolveSingleTemplate(dartSource); + Source childSource = newSource('/child_file.dart', childCode); + newSource('/test.html', ''); + View view; + { + LibrarySpecificUnit target = new LibrarySpecificUnit(source, source); + computeResult(target, VIEWS_WITH_HTML_TEMPLATES1); + expect(task, new isInstanceOf()); + List views; + views = outputs[VIEWS_WITH_HTML_TEMPLATES1]; + expect(views, hasLength(1)); + view = views.first; + } + computeResult(view, HTML_TEMPLATE); + Template template = outputs[HTML_TEMPLATE]; + expect(template, isNotNull); } void _addDartSource(String code) { @@ -100,12 +135,12 @@ class SomeComponent { import '/angular2/angular2.dart'; $code '''; - dartSource = newSource('/test_panel.dart', dartCode); + dartSource = newSource('/test_panel.dart#inline', dartCode); } void _addHtmlSource(String code) { htmlCode = code; - htmlSource = newSource('/test_panel.html', htmlCode); + htmlSource = newSource('/test_panel.html#inline', htmlCode); } void _resolveSingleTemplate(Source dartSource) { diff --git a/analyzer_plugin/test/tasks_test.dart b/analyzer_plugin/test/tasks_test.dart index 1ba6e42d..fb04998c 100644 --- a/analyzer_plugin/test/tasks_test.dart +++ b/analyzer_plugin/test/tasks_test.dart @@ -76,7 +76,7 @@ class AngularParseHtmlTaskTest extends AbstractAngularTest { } test_perform() { - //TODO: rever back once DOCTYPE is implement + //TODO: Max: revert back once DOCTYPE is implement // String code = r''' // // @@ -101,7 +101,7 @@ class AngularParseHtmlTaskTest extends AbstractAngularTest { AnalysisTarget target = newSource('/test.html', code); computeResult(target, ANGULAR_HTML_DOCUMENT); expect(task, new isInstanceOf()); - //expect(outputs[ANGULAR_HTML_DOCUMENT_ERRORS], isEmpty); + expect(outputs[ANGULAR_HTML_DOCUMENT_ERRORS], isEmpty); // HTML_DOCUMENT { List asts = outputs[ANGULAR_HTML_DOCUMENT]; @@ -132,7 +132,7 @@ class AngularParseHtmlTaskTest extends AbstractAngularTest { expect((asts[2] as ElementAst).name, 'span'); } // it's OK to don't have DOCTYPE - //expect(outputs[ANGULAR_HTML_DOCUMENT_ERRORS], isEmpty); + expect(outputs[ANGULAR_HTML_DOCUMENT_ERRORS], isEmpty); } test_perform_noDocType_with_dangling_unclosed_tag() { @@ -2306,7 +2306,7 @@ class ComponentA { errorListener.assertNoErrors(); } - void test_htmlParsing_hasError() { + void test_angularParsing_hasError() { String code = r''' import '/angular2/angular2.dart'; @@ -2319,7 +2319,10 @@ class TextPanel { computeResult(source, DART_ERRORS); // has errors fillErrorListener(DART_ERRORS); - errorListener.assertErrorsWithCodes([HtmlErrorCode.PARSE_ERROR]); + errorListener.assertErrorsWithCodes([ + AngularWarningCode.ANGULAR_PARSER_ERROR, + AngularWarningCode.ANGULAR_PARSER_ERROR, + ]); } void test_input_OK_event() { @@ -2564,6 +2567,7 @@ import '/angular2/angular2.dart'; @Component(selector: 'text-panel', template: r"
text}}
") class TextPanel { + String text; } '''; Source source = newSource('/test.dart', code); @@ -2665,7 +2669,7 @@ class TextPanel { errorListener.assertNoErrors(); } - void test_resolveGetChildDirectivesNgContentSelectors() { + void test_resolveGetChildDirectivesNgContentSelectors_in_template() { String code = r''' import '/angular2/angular2.dart'; import 'child_file.dart'; @@ -2918,7 +2922,7 @@ class TextPanel { } } - void test_resolveGetChildDirectivesNgContentSelectors() { + void test_resolveGetChildDirectivesNgContentSelectors_in_templateUrl() { String code = r''' import '/angular2/angular2.dart'; import 'child_file.dart'; From df8ddc0a9ab60c5eaaa539849c90dd3cc52b25d8 Mon Sep 17 00:00:00 2001 From: Max Kim Date: Sun, 12 Mar 2017 16:41:31 -0700 Subject: [PATCH 07/41] Checkpoint before error code fix --- analyzer_plugin/lib/src/converter.dart | 26 +++-- analyzer_plugin/lib/src/tasks.dart | 12 ++- analyzer_plugin/test/converter_test.dart | 121 ++++------------------- 3 files changed, 44 insertions(+), 115 deletions(-) diff --git a/analyzer_plugin/lib/src/converter.dart b/analyzer_plugin/lib/src/converter.dart index 29bd489a..9d7a8658 100644 --- a/analyzer_plugin/lib/src/converter.dart +++ b/analyzer_plugin/lib/src/converter.dart @@ -206,9 +206,13 @@ class HtmlTreeConverter { } else if (attribute.name == 'template') { attributes.add(_convertTemplateAttribute(attribute)); } else { + String value; + int valueOffset; ParsedAttributeAst _attr = attribute as ParsedAttributeAst; - String value = _attr.valueToken.innerValue.lexeme; - int valueOffset = _attr.valueToken.innerValue.offset; + if (_attr.valueToken != null) { + value = _attr.valueToken.innerValue.lexeme; + valueOffset = _attr.valueToken.innerValue.offset; + } attributes.add(new TextAttribute(_attr.name, _attr.nameOffset, value, valueOffset, dartParser.findMustaches(value, valueOffset))); } @@ -240,9 +244,13 @@ class HtmlTreeConverter { }); element.references.forEach((reference) { + String value; + int valueOffset; ParsedReferenceAst _attr = reference as ParsedReferenceAst; - String value = _attr.valueToken.innerValue.lexeme; - int valueOffset = _attr.valueToken.innerValue.offset; + if (_attr.valueToken != null) { + value = _attr.valueToken.innerValue.lexeme; + valueOffset = _attr.valueToken.innerValue.offset; + } attributes.add(new TextAttribute( _attr.prefixToken.lexeme + _attr.nameToken.lexeme, _attr.prefixToken.offset, @@ -341,7 +349,7 @@ class HtmlTreeConverter { origNameOffset = ast.nameOffset; value = ast.value; - if (value == null) { + if (value == null || value.isEmpty) { errorListener.onError(new AnalysisError( templateSource, origNameOffset, @@ -360,7 +368,7 @@ class HtmlTreeConverter { origNameOffset = ast.prefixToken.offset; value = ast.value; - if (value == null) { + if (value == null || value.isEmpty) { errorListener.onError(new AnalysisError( templateSource, origNameOffset, @@ -400,7 +408,7 @@ class HtmlTreeConverter { origNameOffset = ast.nameOffset; value = ast.value; - if (value == null || value == "") { + if (value == null) { errorListener.onError(new AnalysisError(templateSource, origNameOffset, origName.length, AngularWarningCode.EMPTY_BINDING, [origName])); } @@ -416,7 +424,7 @@ class HtmlTreeConverter { origNameOffset = ast.prefixToken.offset; value = ast.value; - if (value == null || value == "") { + if (value == null) { errorListener.onError(new AnalysisError(templateSource, origNameOffset, origName.length, AngularWarningCode.EMPTY_BINDING, [origName])); } @@ -432,7 +440,7 @@ class HtmlTreeConverter { origNameOffset = ast.prefixToken.offset; value = ast.value; - if (value == null || value == "") { + if (value == null) { errorListener.onError(new AnalysisError(templateSource, origNameOffset, origName.length, AngularWarningCode.EMPTY_BINDING, [origName])); } diff --git a/analyzer_plugin/lib/src/tasks.dart b/analyzer_plugin/lib/src/tasks.dart index b87c228e..d999605f 100644 --- a/analyzer_plugin/lib/src/tasks.dart +++ b/analyzer_plugin/lib/src/tasks.dart @@ -1591,8 +1591,16 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask source, errorListener, typeProvider, errorReporter); template.view.template = template; - template.ast = new HtmlTreeConverter(parser, source, errorListener) - .convertFromAstList(documentAsts); + //TODO: Max: Once done debugging, remove and uncomment duplicate below. + try { + template.ast = new HtmlTreeConverter(parser, source, errorListener) + .convertFromAstList(documentAsts); + } catch(e, stacktrace) { + print(stacktrace); + } + +// template.ast = new HtmlTreeConverter(parser, source, errorListener) +// .convertFromAstList(documentAsts); _setIgnoredErrors(template, documentAsts); template.ast.accept(new NgContentRecorder(template, errorReporter)); diff --git a/analyzer_plugin/test/converter_test.dart b/analyzer_plugin/test/converter_test.dart index 9329dc90..9d60c6a6 100644 --- a/analyzer_plugin/test/converter_test.dart +++ b/analyzer_plugin/test/converter_test.dart @@ -24,110 +24,23 @@ class ConverterTest extends AbstractAngularTest { Template template; List ranges; -// -// void test_inline_conversion() { -// String code = r''' -//import '/angular2/angular2.dart'; -// -//@Component(selector: 'text-panel', -// template: r"
{{text.length + text}}
") -//class TextPanel { -// String text; -//} -//'''; -// Source source = newSource('/test.dart', code); -// LibrarySpecificUnit target = new LibrarySpecificUnit(source, source); -// //computeResult(target, DART_TEMPLATES); -// computeResult(target, VIEWS1); -// final view = outputs[VIEWS1][0]; -// print(view.template); -// print(view.templateText); -// } -// -// void test_event_attribute() { -// _addDartSource(r''' -//import 'dart:html'; -//@Component(selector: 'test-panel') -//@View(templateUrl: 'test_panel.html') -//class TestPanel { -// void handleClick(MouseEvent e) { -// } -//} -//'''); -// _addHtmlSource(r""" -//
-//"""); -// -// computeResult( -// new LibrarySpecificUnit(dartSource, dartSource), ANGULAR_ASTS); -// _resolveSingleTemplate(dartSource); -// expect(template.ast, new isInstanceOf()); -// ElementInfo root = template.ast; -// expect(root.localName, 'html'); -// expect(root.childNodes.length, 2); -// ElementInfo div = root.childNodes[0]; -// expect(div.localName, 'div'); -// expect(div.attributes.length, 1); -// AttributeInfo event = div.attributes[0]; -// expect(event.originalName, '(click)'); -// expect(event.name, 'click'); -// expect(event.value, 'handleClick(\$event)'); -// } -// -// void test_property_attribute() { -// _addDartSource(r''' -//import 'dart:html'; -//@Component(selector: 'test-panel') -//@View(templateUrl: 'test_panel.html') -//class TestPanel { -// void handleClick(MouseEvent e) { -// } -//} -//'''); -// _addHtmlSource(r""" -//
-// -//
-//"""); -// computeResult( -// new LibrarySpecificUnit(dartSource, dartSource), ANGULAR_ASTS); -// _resolveSingleTemplate(dartSource); -// -// } void test_scratch() { - String code = r''' -import '/angular2/angular2.dart'; -import 'child_file.dart'; - -import '/angular2/angular2.dart'; -@Component(selector: 'my-component', templateUrl: 'test.html', - directives: const [ChildComponent]) -class MyComponent {} -'''; - String childCode = r''' -import '/angular2/angular2.dart'; -@Component(selector: 'child-component', - template: 'My template ', - directives: const []) -class ChildComponent {} -'''; - Source source = newSource('/test.dart', code); - Source childSource = newSource('/child_file.dart', childCode); - newSource('/test.html', ''); - View view; - { - LibrarySpecificUnit target = new LibrarySpecificUnit(source, source); - computeResult(target, VIEWS_WITH_HTML_TEMPLATES1); - expect(task, new isInstanceOf()); - List views; - views = outputs[VIEWS_WITH_HTML_TEMPLATES1]; - expect(views, hasLength(1)); - view = views.first; - } - computeResult(view, HTML_TEMPLATE); - Template template = outputs[HTML_TEMPLATE]; - expect(template, isNotNull); + _addDartSource(r''' +@Component(selector: 'test-panel', + directives: const [TitleComponent], templateUrl: 'test_panel.html') +class TestPanel { + String text; // 1 +} +@Directive(selector: '[titled]', template: '', inputs: 'title') +class TitleComponent { + @Input() String title; +} +'''); + _addHtmlSource(r""" + +"""); + _resolveSingleTemplate(dartSource); } void _addDartSource(String code) { @@ -135,12 +48,12 @@ class ChildComponent {} import '/angular2/angular2.dart'; $code '''; - dartSource = newSource('/test_panel.dart#inline', dartCode); + dartSource = newSource('/test_panel.dart', dartCode); } void _addHtmlSource(String code) { htmlCode = code; - htmlSource = newSource('/test_panel.html#inline', htmlCode); + htmlSource = newSource('/test_panel.html', htmlCode); } void _resolveSingleTemplate(Source dartSource) { From ff0be32ac220367598f242c7f0c3743f09d3307f Mon Sep 17 00:00:00 2001 From: Max Kim Date: Fri, 17 Mar 2017 19:15:00 -0700 Subject: [PATCH 08/41] Tasks tests set back to 100% after introducing error codes --- analyzer_plugin/lib/src/tasks.dart | 23 ++++++++--------------- analyzer_plugin/lib/tasks.dart | 6 ------ analyzer_plugin/test/tasks_test.dart | 24 +++++++++++++----------- 3 files changed, 21 insertions(+), 32 deletions(-) diff --git a/analyzer_plugin/lib/src/tasks.dart b/analyzer_plugin/lib/src/tasks.dart index d999605f..be4aecc7 100644 --- a/analyzer_plugin/lib/src/tasks.dart +++ b/analyzer_plugin/lib/src/tasks.dart @@ -326,21 +326,14 @@ abstract class ParseHtmlMixin implements AnalysisTask { ); for (NgAst.AngularParserException e in exceptionHandler.exceptions) { - ErrorCode errorCode; - if (e.message == 'Unopened mustache') { - errorCode = AngularWarningCode.UNOPENED_MUSTACHE; - } else if (e.message == 'Unclosed mustache') { - errorCode = AngularWarningCode.UNTERMINATED_MUSTACHE; - } else { - errorCode = AngularWarningCode.ANGULAR_PARSER_ERROR; + if (e.errorCode is NgAst.NgParserWarningCode) { + this.parseErrors.add(new AnalysisError( + target.source, + e.offset, + e.length, + e.errorCode, + )); } - - this.parseErrors.add(new AnalysisError( - target.source, - e.offset, - e.context.length, - errorCode, - )); } } } @@ -1595,7 +1588,7 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask try { template.ast = new HtmlTreeConverter(parser, source, errorListener) .convertFromAstList(documentAsts); - } catch(e, stacktrace) { + } catch (e, stacktrace) { print(stacktrace); } diff --git a/analyzer_plugin/lib/tasks.dart b/analyzer_plugin/lib/tasks.dart index 926528bd..bc376740 100644 --- a/analyzer_plugin/lib/tasks.dart +++ b/analyzer_plugin/lib/tasks.dart @@ -9,12 +9,6 @@ import 'package:analyzer/error/error.dart'; * and, when appropriate, how the problem can be corrected. */ class AngularWarningCode extends ErrorCode { - /** - * An error code indicating that the error originated from Angular AST parse. - */ - static const AngularWarningCode ANGULAR_PARSER_ERROR = - const AngularWarningCode('ANGULAR_PARSER_ERROR', 'Unexpected token'); - /** * An error code indicating that the annotation does not define the * required "selector" argument. diff --git a/analyzer_plugin/test/tasks_test.dart b/analyzer_plugin/test/tasks_test.dart index fb04998c..96943041 100644 --- a/analyzer_plugin/test/tasks_test.dart +++ b/analyzer_plugin/test/tasks_test.dart @@ -2320,8 +2320,8 @@ class TextPanel { // has errors fillErrorListener(DART_ERRORS); errorListener.assertErrorsWithCodes([ - AngularWarningCode.ANGULAR_PARSER_ERROR, - AngularWarningCode.ANGULAR_PARSER_ERROR, + NgParserWarningCode.DANGLING_CLOSE_ELEMENT, + NgParserWarningCode.CANNOT_FIND_MATCHING_CLOSE, ]); } @@ -2548,7 +2548,7 @@ class TextPanel { String code = r''' import '/angular2/angular2.dart'; -@Component(selector: 'text-panel', template: r"
{{text
") +@Component(selector: 'text-panel', template: r"{{text") class TextPanel { String text = "text"; } @@ -2557,8 +2557,9 @@ class TextPanel { computeResult(source, DART_ERRORS); // has errors fillErrorListener(DART_ERRORS); - errorListener - .assertErrorsWithCodes([AngularWarningCode.UNTERMINATED_MUSTACHE]); + errorListener.assertErrorsWithCodes([ + NgParserWarningCode.AFTER_INTERPOLATION, + ]); } void test_textExpression_hasError_UnopenedMustache() { @@ -2574,7 +2575,8 @@ class TextPanel { computeResult(source, DART_ERRORS); // has errors fillErrorListener(DART_ERRORS); - errorListener.assertErrorsWithCodes([AngularWarningCode.UNOPENED_MUSTACHE]); + errorListener + .assertErrorsWithCodes([NgParserWarningCode.BEFORE_INTERPOLATION]); } void test_textExpression_hasError_DoubleOpenedMustache() { @@ -2591,7 +2593,7 @@ class TextPanel { // has errors fillErrorListener(DART_ERRORS); errorListener.assertErrorsWithCodes([ - AngularWarningCode.UNTERMINATED_MUSTACHE, + NgParserWarningCode.AFTER_INTERPOLATION, StaticWarningCode.UNDEFINED_IDENTIFIER ]); } @@ -2610,11 +2612,11 @@ class TextPanel { // has errors fillErrorListener(DART_ERRORS); errorListener.assertErrorsWithCodes([ - AngularWarningCode.UNTERMINATED_MUSTACHE, - AngularWarningCode.UNTERMINATED_MUSTACHE, + NgParserWarningCode.AFTER_INTERPOLATION, + NgParserWarningCode.AFTER_INTERPOLATION, StaticWarningCode.UNDEFINED_IDENTIFIER, - AngularWarningCode.UNOPENED_MUSTACHE, - AngularWarningCode.UNOPENED_MUSTACHE + NgParserWarningCode.BEFORE_INTERPOLATION, + NgParserWarningCode.BEFORE_INTERPOLATION, ]); } From 390c09d92a6da3fc28a736e91bc191e34bc03bf3 Mon Sep 17 00:00:00 2001 From: Max Kim Date: Sat, 18 Mar 2017 22:14:02 -0700 Subject: [PATCH 09/41] resolver_test midway checkpoint --- analyzer_plugin/lib/src/converter.dart | 59 ++++++++++++++-------- analyzer_plugin/lib/src/tasks.dart | 26 ++++++---- analyzer_plugin/lib/tasks.dart | 1 + analyzer_plugin/test/abstract_angular.dart | 46 ++++++++--------- analyzer_plugin/test/resolver_test.dart | 39 +++++++++----- analyzer_plugin/test/tasks_test.dart | 18 +++---- 6 files changed, 113 insertions(+), 76 deletions(-) diff --git a/analyzer_plugin/lib/src/converter.dart b/analyzer_plugin/lib/src/converter.dart index 9d7a8658..50494186 100644 --- a/analyzer_plugin/lib/src/converter.dart +++ b/analyzer_plugin/lib/src/converter.dart @@ -343,7 +343,7 @@ class HtmlTreeConverter { String origName; int origNameOffset; - // TODO: refactor once a generic DecoratorAst is created + // TODO: Max: refactor once a generic DecoratorAst is created if (ast is ParsedAttributeAst) { origName = ast.name; origNameOffset = ast.nameOffset; @@ -363,23 +363,23 @@ class HtmlTreeConverter { propNameOffset = origNameOffset + prefix.length; } else if (ast is ParsedEventAst) { origName = ast.prefixToken.lexeme + - ast.nameToken.lexeme + + ast.name + + (ast.postfix != null ? '.${ast.postfix}' : '') + ast.suffixToken.lexeme; origNameOffset = ast.prefixToken.offset; value = ast.value; - if (value == null || value.isEmpty) { - errorListener.onError(new AnalysisError( - templateSource, - origNameOffset, - ast.nameToken.length, - AngularWarningCode.EMPTY_BINDING, - [ast.name])); + + if ((value == null || value.isEmpty) && + !ast.prefixToken.errorSynthetic && + !ast.suffixToken.errorSynthetic) { + errorListener.onError(new AnalysisError(templateSource, origNameOffset, + origName.length, AngularWarningCode.EMPTY_BINDING, [ast.name])); } valueOffset = ast.valueOffset; propName = _removePrefixSuffix(origName, prefix, suffix); - propNameOffset = origNameOffset + prefix.length; + propNameOffset = ast.nameToken.offset; } return new StatementsBoundAttribute( propName, @@ -408,30 +408,39 @@ class HtmlTreeConverter { origNameOffset = ast.nameOffset; value = ast.value; - if (value == null) { + if (value == null || value.isEmpty) { errorListener.onError(new AnalysisError(templateSource, origNameOffset, origName.length, AngularWarningCode.EMPTY_BINDING, [origName])); } valueOffset = ast.valueOffset; propName = _removePrefixSuffix(origName, prefix, suffix); - propNameOffset = origNameOffset + prefix.length; + propNameOffset = ast.nameToken.offset; } if (ast is ParsedPropertyAst) { origName = ast.prefixToken.lexeme + - ast.nameToken.lexeme + + ast.name + + (ast.postfix != null ? '.${ast.postfix}' : '') + + (ast.unit != null ? '.${ast.unit}' : '') + ast.suffixToken.lexeme; origNameOffset = ast.prefixToken.offset; value = ast.value; - if (value == null) { - errorListener.onError(new AnalysisError(templateSource, origNameOffset, - origName.length, AngularWarningCode.EMPTY_BINDING, [origName])); + if ((value == null || value.isEmpty) && + !ast.prefixToken.errorSynthetic && + !ast.suffixToken.errorSynthetic) { + errorListener.onError(new AnalysisError( + templateSource, + origNameOffset, + origName.length, + AngularWarningCode.EMPTY_BINDING, + [origName], + )); } valueOffset = ast.valueOffset; propName = _removePrefixSuffix(origName, prefix, suffix); - propNameOffset = origNameOffset + prefix.length; + propNameOffset = ast.nameToken.offset; } if (ast is ParsedBananaAst) { origName = ast.prefixToken.lexeme + @@ -440,9 +449,16 @@ class HtmlTreeConverter { origNameOffset = ast.prefixToken.offset; value = ast.value; - if (value == null) { - errorListener.onError(new AnalysisError(templateSource, origNameOffset, - origName.length, AngularWarningCode.EMPTY_BINDING, [origName])); + if ((value == null || value.isEmpty) && + !ast.prefixToken.errorSynthetic && + !ast.suffixToken.errorSynthetic) { + errorListener.onError(new AnalysisError( + templateSource, + origNameOffset, + origName.length, + AngularWarningCode.EMPTY_BINDING, + [origName], + )); } valueOffset = ast.valueOffset; @@ -673,6 +689,9 @@ class EmbeddedDartParser { } // resolve String code = text.substring(exprBegin, exprEnd); + if (code.trim().isEmpty) { + continue; + } Expression expression = parseDartExpression(fileOffset + exprBegin, code, detectTrailing); diff --git a/analyzer_plugin/lib/src/tasks.dart b/analyzer_plugin/lib/src/tasks.dart index be4aecc7..77ab3662 100644 --- a/analyzer_plugin/lib/src/tasks.dart +++ b/analyzer_plugin/lib/src/tasks.dart @@ -316,6 +316,18 @@ abstract class ParseHtmlMixin implements AnalysisTask { List documentAsts; final parseErrors = []; + // TODO: Max - ideally remove this mapping and make errors exclusive to be + // TODO: generated from either angular_ast or angular_analyzer, but not both. + // Need to keep UNTERMINATED_MUSTACHE and UNOPENED_MUSTACHE because + // angular_ast currently does not do parsing for values in quotes; avoiding + // duplicate/redundant error types. + static const errorMap = const { + NgAst.NgParserWarningCode.AFTER_INTERPOLATION: + AngularWarningCode.UNTERMINATED_MUSTACHE, + NgAst.NgParserWarningCode.BEFORE_INTERPOLATION: + AngularWarningCode.UNOPENED_MUSTACHE, + }; + void parse(String content, String sourceUrl) { final exceptionHandler = new NgAst.RecoveringExceptionHandler(); documentAsts = NgAst.parse( @@ -331,7 +343,7 @@ abstract class ParseHtmlMixin implements AnalysisTask { target.source, e.offset, e.length, - e.errorCode, + errorMap[e.errorCode] ?? e.errorCode, )); } } @@ -1584,16 +1596,8 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask source, errorListener, typeProvider, errorReporter); template.view.template = template; - //TODO: Max: Once done debugging, remove and uncomment duplicate below. - try { - template.ast = new HtmlTreeConverter(parser, source, errorListener) - .convertFromAstList(documentAsts); - } catch (e, stacktrace) { - print(stacktrace); - } - -// template.ast = new HtmlTreeConverter(parser, source, errorListener) -// .convertFromAstList(documentAsts); + template.ast = new HtmlTreeConverter(parser, source, errorListener) + .convertFromAstList(documentAsts); _setIgnoredErrors(template, documentAsts); template.ast.accept(new NgContentRecorder(template, errorReporter)); diff --git a/analyzer_plugin/lib/tasks.dart b/analyzer_plugin/lib/tasks.dart index bc376740..70320059 100644 --- a/analyzer_plugin/lib/tasks.dart +++ b/analyzer_plugin/lib/tasks.dart @@ -240,6 +240,7 @@ class AngularWarningCode extends ErrorCode { * identifier * https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier */ + // TODO: Max: Potentially deprecated with usage of angular_ast. static const AngularWarningCode INVALID_CSS_PROPERTY_NAME = const AngularWarningCode('INVALID_CSS_PROPERTY_NAME', 'The css property {0} is not a valid css identifier'); diff --git a/analyzer_plugin/test/abstract_angular.dart b/analyzer_plugin/test/abstract_angular.dart index a205b34d..74ef36bf 100644 --- a/analyzer_plugin/test/abstract_angular.dart +++ b/analyzer_plugin/test/abstract_angular.dart @@ -19,6 +19,7 @@ import 'package:angular_analyzer_plugin/src/selector.dart'; import 'package:angular_analyzer_plugin/src/tasks.dart'; import 'package:plugin/manager.dart'; import 'package:plugin/plugin.dart'; +import 'package:tuple/tuple.dart'; import 'package:unittest/unittest.dart'; import 'mock_sdk.dart'; @@ -321,30 +322,29 @@ class NgFor { expect(errorListener.errors.single.length, snippet.length); } -/** - * Assert multiple [errCode] is reported for [code], highlighting the [snippet]. - */ - void assertMultipleErrorsInCodeAtPositions( - String code, Map errCodesAndSnippet) { - Map> expectedErrors = new Map(); - errCodesAndSnippet.forEach((errCode, snippet) { - int snippetIndex = code.indexOf(snippet); - expect(snippetIndex, greaterThan(-1), - reason: 'Error in test: snippet ${snippet} not part of code ${code}'); - Map currErrorList = expectedErrors.putIfAbsent(errCode, () => new Map()); - currErrorList.putIfAbsent(snippetIndex, () => snippet); - }); - errorListener.assertErrorsWithCodes(expectedErrors.keys); - - List errors = errorListener.errors; - errors.forEach((currErr) { - expect(expectedErrors.containsKey(currErr.errorCode), true); - expect( - expectedErrors[currErr.errorCode].containsKey(currErr.offset), true); - expect(currErr.length, - expectedErrors[currErr.errorCode][currErr.offset].length, - verbose: true); + /** + * Given an explicit list of [AnalysisError], check to see if errors + * occurred during angular analysis. + */ + // TODO: Max: remove debug flag. + void assertMultipleErrorsExplicit(List expectedErrors, {bool debug: false}) { + var realErrors = errorListener.errors; + if (debug) { + realErrors.forEach((error) { + print(error.errorCode); + print(error.isStaticOnly); + print(error.source); + print(error.offset); + print(error.length); + }); + } + expectedErrors.forEach((expectedError) { + expect(realErrors.contains(expectedError), true, + reason: 'Expected error code ${expectedError.errorCode} never occurs at ' + 'location ${expectedError.offset} of length ${expectedError.length}.'); }); + expect(realErrors.length, expectedErrors.length, + reason: 'Expected error counts do not match.'); } } diff --git a/analyzer_plugin/test/resolver_test.dart b/analyzer_plugin/test/resolver_test.dart index 47a02f8e..21c0bd6a 100644 --- a/analyzer_plugin/test/resolver_test.dart +++ b/analyzer_plugin/test/resolver_test.dart @@ -1,6 +1,7 @@ library angular2.src.analysis.analyzer_plugin.src.resolver_test; import 'package:analyzer/src/generated/source.dart'; +import 'package:analyzer/error/error.dart'; import 'package:analyzer/src/error/codes.dart'; import 'package:analyzer/src/dart/error/syntactic_errors.dart'; import 'package:analyzer/task/dart.dart'; @@ -9,7 +10,9 @@ import 'package:angular_analyzer_plugin/src/model.dart'; import 'package:angular_analyzer_plugin/src/selector.dart'; import 'package:angular_analyzer_plugin/src/tasks.dart'; import 'package:angular_analyzer_plugin/tasks.dart'; +import 'package:angular_ast/angular_ast.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; +import 'package:tuple/tuple.dart'; import 'package:unittest/unittest.dart'; import 'abstract_angular.dart'; @@ -552,7 +555,7 @@ class TestPanel { _addHtmlSource(code); _resolveSingleTemplate(dartSource); assertErrorInCodeAtPosition( - AngularWarningCode.UNOPENED_MUSTACHE, code, "}}"); + AngularWarningCode.UNOPENED_MUSTACHE, code, '}}'); } void test_expression_as_not_allowed() { @@ -969,7 +972,7 @@ class TestPanel { _addHtmlSource(code); _resolveSingleTemplate(dartSource); assertErrorInCodeAtPosition( - AngularWarningCode.INVALID_HTML_CLASSNAME, code, "invalid.class"); + AngularWarningCode.INVALID_HTML_CLASSNAME, code, "class.invalid"); } void test_expression_classBinding_typeError() { @@ -1015,8 +1018,12 @@ class TestPanel { """; _addHtmlSource(code); _resolveSingleTemplate(dartSource); - assertErrorInCodeAtPosition( - AngularWarningCode.INVALID_CSS_PROPERTY_NAME, code, "invalid*property"); + assertMultipleErrorsExplicit([ + new AnalysisError( + htmlSource, 29, 0, AngularWarningCode.NONEXIST_INPUT_BOUND, ['']), + new AnalysisError(htmlSource, 29, 1, NgParserWarningCode.AFTER_DECORATOR_NEED_WHITESPACE), + new AnalysisError(htmlSource, 6, 14, NgParserWarningCode.SUFFIX_PROPERTY), + ]); } void test_expression_styleBinding_noUnit_expressionTypeError() { @@ -1047,8 +1054,12 @@ class TestPanel { """; _addHtmlSource(code); _resolveSingleTemplate(dartSource); - assertErrorInCodeAtPosition( - AngularWarningCode.INVALID_CSS_PROPERTY_NAME, code, "border&radius"); + assertMultipleErrorsExplicit([ + new AnalysisError(htmlSource, 29, 0, AngularWarningCode.NONEXIST_INPUT_BOUND, ['']), + new AnalysisError(htmlSource, 29, 1, NgParserWarningCode.AFTER_DECORATOR_NEED_WHITESPACE), + new AnalysisError(htmlSource, 19, 1, NgParserWarningCode.UNEXPECTED_TOKEN), + new AnalysisError(htmlSource, 6, 14, NgParserWarningCode.SUFFIX_PROPERTY), + ]); } void test_expression_styleBinding_withUnit_invalidUnitName() { @@ -1063,8 +1074,12 @@ class TestPanel { """; _addHtmlSource(code); _resolveSingleTemplate(dartSource); - assertErrorInCodeAtPosition( - AngularWarningCode.INVALID_CSS_UNIT_NAME, code, "p|x"); + assertMultipleErrorsExplicit([ + new AnalysisError(htmlSource, 30, 0, AngularWarningCode.NONEXIST_INPUT_BOUND, ['']), + new AnalysisError(htmlSource, 30, 1, NgParserWarningCode.AFTER_DECORATOR_NEED_WHITESPACE), + new AnalysisError(htmlSource, 28, 1, NgParserWarningCode.UNEXPECTED_TOKEN), + new AnalysisError(htmlSource, 6, 23, NgParserWarningCode.SUFFIX_PROPERTY), + ]); } void test_expression_styleBinding_withUnit_typeError() { @@ -1729,10 +1744,10 @@ class TestPanel { """; _addHtmlSource(code); _resolveSingleTemplate(dartSource); - assertMultipleErrorsInCodeAtPositions(code, { - ParserErrorCode.UNEXPECTED_TOKEN: '}', - StaticTypeWarningCode.UNDEFINED_GETTER: 'length' - }); + assertMultipleErrorsExplicit([ + new AnalysisError(htmlSource, 14, 1, ParserErrorCode.UNEXPECTED_TOKEN), + new AnalysisError(htmlSource, 17, 6, StaticTypeWarningCode.UNDEFINED_GETTER), + ]); } void test_inheritedFields() { diff --git a/analyzer_plugin/test/tasks_test.dart b/analyzer_plugin/test/tasks_test.dart index 96943041..4c5061ad 100644 --- a/analyzer_plugin/test/tasks_test.dart +++ b/analyzer_plugin/test/tasks_test.dart @@ -2557,9 +2557,8 @@ class TextPanel { computeResult(source, DART_ERRORS); // has errors fillErrorListener(DART_ERRORS); - errorListener.assertErrorsWithCodes([ - NgParserWarningCode.AFTER_INTERPOLATION, - ]); + errorListener + .assertErrorsWithCodes([AngularWarningCode.UNTERMINATED_MUSTACHE]); } void test_textExpression_hasError_UnopenedMustache() { @@ -2575,8 +2574,7 @@ class TextPanel { computeResult(source, DART_ERRORS); // has errors fillErrorListener(DART_ERRORS); - errorListener - .assertErrorsWithCodes([NgParserWarningCode.BEFORE_INTERPOLATION]); + errorListener.assertErrorsWithCodes([AngularWarningCode.UNOPENED_MUSTACHE]); } void test_textExpression_hasError_DoubleOpenedMustache() { @@ -2593,7 +2591,7 @@ class TextPanel { // has errors fillErrorListener(DART_ERRORS); errorListener.assertErrorsWithCodes([ - NgParserWarningCode.AFTER_INTERPOLATION, + AngularWarningCode.UNTERMINATED_MUSTACHE, StaticWarningCode.UNDEFINED_IDENTIFIER ]); } @@ -2612,11 +2610,11 @@ class TextPanel { // has errors fillErrorListener(DART_ERRORS); errorListener.assertErrorsWithCodes([ - NgParserWarningCode.AFTER_INTERPOLATION, - NgParserWarningCode.AFTER_INTERPOLATION, + AngularWarningCode.UNTERMINATED_MUSTACHE, + AngularWarningCode.UNTERMINATED_MUSTACHE, StaticWarningCode.UNDEFINED_IDENTIFIER, - NgParserWarningCode.BEFORE_INTERPOLATION, - NgParserWarningCode.BEFORE_INTERPOLATION, + AngularWarningCode.UNOPENED_MUSTACHE, + AngularWarningCode.UNOPENED_MUSTACHE, ]); } From 931979194285f2ef78f7ea5a5c6dc6b332861633 Mon Sep 17 00:00:00 2001 From: Max Kim Date: Sun, 19 Mar 2017 18:37:26 -0700 Subject: [PATCH 10/41] Checkpoint --- analyzer_plugin/lib/src/converter.dart | 8 +++--- analyzer_plugin/lib/src/tasks.dart | 17 ++++++++----- analyzer_plugin/test/abstract_angular.dart | 1 + analyzer_plugin/test/converter_test.dart | 29 +++++++++++++++------- analyzer_plugin/test/resolver_test.dart | 8 +++--- deps/pubspec.yaml | 2 +- 6 files changed, 40 insertions(+), 25 deletions(-) diff --git a/analyzer_plugin/lib/src/converter.dart b/analyzer_plugin/lib/src/converter.dart index 50494186..5598f8e3 100644 --- a/analyzer_plugin/lib/src/converter.dart +++ b/analyzer_plugin/lib/src/converter.dart @@ -183,7 +183,7 @@ class HtmlTreeConverter { } if (node is InterpolationAst) { int offset = node.sourceSpan.start.offset; - String text = "{{" + node.value + "}}"; + String text = '{{' + node.value + '}}'; return new TextInfo( offset, text, parent, dartParser.findMustaches(text, offset)); } @@ -291,9 +291,7 @@ class HtmlTreeConverter { String fullAstName; if (value != null) { fullAstName = ast.name + - (' ' * (ast.equalSignOffset - ast.nameToken.end)) + - ' ' + - (' ' * (ast.valueToken.offset - ast.equalSignToken.end)) + + (' ' * (ast.valueToken.innerValue.offset - ast.nameToken.end)) + (value ?? ''); } else { fullAstName = ast.name + ' '; @@ -415,7 +413,7 @@ class HtmlTreeConverter { valueOffset = ast.valueOffset; propName = _removePrefixSuffix(origName, prefix, suffix); - propNameOffset = ast.nameToken.offset; + propNameOffset = ast.nameToken.offset + (prefix?.length ?? 0); } if (ast is ParsedPropertyAst) { origName = ast.prefixToken.lexeme + diff --git a/analyzer_plugin/lib/src/tasks.dart b/analyzer_plugin/lib/src/tasks.dart index 77ab3662..84b2447f 100644 --- a/analyzer_plugin/lib/src/tasks.dart +++ b/analyzer_plugin/lib/src/tasks.dart @@ -330,12 +330,17 @@ abstract class ParseHtmlMixin implements AnalysisTask { void parse(String content, String sourceUrl) { final exceptionHandler = new NgAst.RecoveringExceptionHandler(); - documentAsts = NgAst.parse( - content, - sourceUrl: sourceUrl, - desugar: false, - exceptionHandler: exceptionHandler, - ); + // TODO: Max: Remove try-catch after finished integration. + try { + documentAsts = NgAst.parse( + content, + sourceUrl: sourceUrl, + desugar: false, + exceptionHandler: exceptionHandler, + ); + } catch (e, stack){ + print(stack); + } for (NgAst.AngularParserException e in exceptionHandler.exceptions) { if (e.errorCode is NgAst.NgParserWarningCode) { diff --git a/analyzer_plugin/test/abstract_angular.dart b/analyzer_plugin/test/abstract_angular.dart index 74ef36bf..7ebfeb13 100644 --- a/analyzer_plugin/test/abstract_angular.dart +++ b/analyzer_plugin/test/abstract_angular.dart @@ -336,6 +336,7 @@ class NgFor { print(error.source); print(error.offset); print(error.length); + print(error.message); }); } expectedErrors.forEach((expectedError) { diff --git a/analyzer_plugin/test/converter_test.dart b/analyzer_plugin/test/converter_test.dart index 9d60c6a6..44a0c892 100644 --- a/analyzer_plugin/test/converter_test.dart +++ b/analyzer_plugin/test/converter_test.dart @@ -27,20 +27,26 @@ class ConverterTest extends AbstractAngularTest { void test_scratch() { _addDartSource(r''' -@Component(selector: 'test-panel', - directives: const [TitleComponent], templateUrl: 'test_panel.html') -class TestPanel { - String text; // 1 -} -@Directive(selector: '[titled]', template: '', inputs: 'title') -class TitleComponent { - @Input() String title; +@Component( + selector: 'name-panel', + inputs: const ['aaa', 'bbb', 'ccc']) +@View(template: r"
AAA
") +class NamePanel { + int aaa; + int bbb; + int ccc; } +@Component(selector: 'test-panel') +@View(templateUrl: 'test_panel.html', directives: const [NamePanel]) +class TestPanel {} '''); _addHtmlSource(r""" - + """); _resolveSingleTemplate(dartSource); + ResolvedRange resolvedRange1 = _findResolvedRange("bbb]="); + ResolvedRange resolvedRange2 = _findResolvedRange('ccc='); + print(resolvedRange2); } void _addDartSource(String code) { @@ -66,4 +72,9 @@ $code ranges = template.ranges; fillErrorListener(HTML_TEMPLATE_ERRORS); } + + ResolvedRange _findResolvedRange(String search, + [ResolvedRangeCondition condition]) { + return getResolvedRangeAtString(htmlCode, ranges, search, condition); + } } diff --git a/analyzer_plugin/test/resolver_test.dart b/analyzer_plugin/test/resolver_test.dart index 21c0bd6a..ded4f40d 100644 --- a/analyzer_plugin/test/resolver_test.dart +++ b/analyzer_plugin/test/resolver_test.dart @@ -1745,8 +1745,8 @@ class TestPanel { _addHtmlSource(code); _resolveSingleTemplate(dartSource); assertMultipleErrorsExplicit([ - new AnalysisError(htmlSource, 14, 1, ParserErrorCode.UNEXPECTED_TOKEN), - new AnalysisError(htmlSource, 17, 6, StaticTypeWarningCode.UNDEFINED_GETTER), + new AnalysisError(htmlSource, 14, 1, ParserErrorCode.UNEXPECTED_TOKEN, ['}']), + new AnalysisError(htmlSource, 17, 6, StaticTypeWarningCode.UNDEFINED_GETTER, ['length', 'int']), ]); } @@ -1824,8 +1824,8 @@ class TestPanel {} expect(search.element.boundDirectives, hasLength(1)); DirectiveBinding boundDirective = search.element.boundDirectives.first; expect(boundDirective.outputBindings, hasLength(2)); - expect(boundDirective.outputBindings[0].boundOutput.name, 'bbb'); - expect(boundDirective.outputBindings[1].boundOutput.name, 'ccc'); + expect(boundDirective.outputBindings[0].boundOutput.name, 'ccc'); + expect(boundDirective.outputBindings[1].boundOutput.name, 'bbb'); } void test_twoWayReference() { diff --git a/deps/pubspec.yaml b/deps/pubspec.yaml index 7b551fc2..f34cd1f2 100644 --- a/deps/pubspec.yaml +++ b/deps/pubspec.yaml @@ -6,7 +6,7 @@ dependencies: angular_ast: git: url: https://github.com/mk13/angular_ast - ref: ast_recovery + ref: error_code dev_dependencies: test_reflective_loader: '^0.0.3' typed_mock: '^0.0.4' From 11a613ae8cbd515f94bb9722b3872722cd9ec3f1 Mon Sep 17 00:00:00 2001 From: Max Kim Date: Mon, 20 Mar 2017 14:51:03 -0700 Subject: [PATCH 11/41] Full test coverage in analyzer. Server still left --- analyzer_plugin/lib/src/converter.dart | 142 ++++++++++++++++++++- analyzer_plugin/lib/src/resolver.dart | 11 +- analyzer_plugin/lib/src/tasks.dart | 2 +- analyzer_plugin/lib/tasks.dart | 10 -- analyzer_plugin/test/abstract_angular.dart | 6 +- analyzer_plugin/test/converter_test.dart | 29 ++--- analyzer_plugin/test/resolver_test.dart | 93 ++++++++------ 7 files changed, 211 insertions(+), 82 deletions(-) diff --git a/analyzer_plugin/lib/src/converter.dart b/analyzer_plugin/lib/src/converter.dart index 5598f8e3..334250d6 100644 --- a/analyzer_plugin/lib/src/converter.dart +++ b/analyzer_plugin/lib/src/converter.dart @@ -67,7 +67,6 @@ class HtmlTreeConverter { if (node is ElementAst) { String localName = node.name; List attributes = _convertAttributes(node); - bool isTemplate = localName == 'template'; final closeComponent = node.closeComplement; SourceRange openingSpan; SourceRange openingNameSpan; @@ -108,7 +107,7 @@ class HtmlTreeConverter { closingSpan, openingNameSpan, closingNameSpan, - isTemplate, + false, attributes, findTemplateAttribute(attributes), parent); @@ -150,12 +149,12 @@ class HtmlTreeConverter { new SourceRange(openingSpan.offset + '<'.length, localName.length); var pnode = node as ParsedEmbeddedContentAst; var valueToken = pnode.selectorValueToken; - if (pnode.selectorValueToken != null) { + if (pnode.selectToken != null) { attributes.add(new TextAttribute( 'select', - pnode.identifierToken.offset, - valueToken.lexeme, - valueToken.offset, + pnode.selectToken.offset, + valueToken?.innerValue?.lexeme, + valueToken?.innerValue?.offset, [], )); } @@ -175,6 +174,72 @@ class HtmlTreeConverter { openingNameSpan, closingNameSpan, false, attributes, null, parent); return ngContent; } + if (node is EmbeddedTemplateAst) { + String localName = 'template'; + List attributes = _convertAttributesForTemplate(node); + final closeComponent = node.closeComplement; + SourceRange openingSpan; + SourceRange openingNameSpan; + SourceRange closingSpan; + SourceRange closingNameSpan; + + if (node.isSynthetic) { + openingSpan = _toSourceRange(closeComponent.beginToken.offset, 0); + openingNameSpan = openingSpan; + } else { + openingSpan = _toSourceRange( + node.beginToken.offset, node.endToken.end - node.beginToken.offset); + openingNameSpan = + new SourceRange(openingSpan.offset + '<'.length, localName.length); + } + // Check for void element cases (has closing complement) + if (closeComponent != null) { + if (closeComponent.isSynthetic) { + closingSpan = _toSourceRange(node.endToken.end, 0); + closingNameSpan = closingSpan; + } else { + closingSpan = _toSourceRange(closeComponent.beginToken.offset, + closeComponent.endToken.end - closeComponent.beginToken.offset); + closingNameSpan = new SourceRange( + closingSpan.offset + ' children = _convertChildren(node, element); + element.childNodes.addAll(children); + + if (!element.isSynthetic && + element.openingSpanIsClosed && + closingSpan != null && + (openingSpan.offset + openingSpan.length) == closingSpan.offset) { + element.childNodes.add(new TextInfo( + openingSpan.offset + openingSpan.length, '', element, [], + synthetic: true)); + } + + return element; + } if (node is TextAst) { int offset = node.sourceSpan.start.offset; String text = node.value; @@ -190,6 +255,7 @@ class HtmlTreeConverter { return null; } + // TODO: Max: Introduce overlapping type and only use single _convertAttributes. List _convertAttributes(ElementAst element) { List attributes = []; @@ -266,6 +332,70 @@ class HtmlTreeConverter { return attributes; } + List _convertAttributesForTemplate( + EmbeddedTemplateAst element) { + List attributes = []; + + // Atttribute/event/properties/etc. within + // [ElementAst] cannot be synthetic as long as Desugaring never occurs. + if (element is EmbeddedTemplateAst) { + element.attributes.forEach((AttributeAst attribute) { + if (attribute.name.startsWith('on-')) { + attributes + .add(_convertStatementsBoundAttribute(attribute, "on-", null)); + } else if (attribute.name.startsWith('bind-')) { + attributes.add(_convertExpressionBoundAttribute( + attribute, "bind-", null, ExpressionBoundType.input)); + } else if (attribute.name == 'template') { + attributes.add(_convertTemplateAttribute(attribute)); + } else { + String value; + int valueOffset; + ParsedAttributeAst _attr = attribute as ParsedAttributeAst; + if (_attr.valueToken != null) { + value = _attr.valueToken.innerValue.lexeme; + valueOffset = _attr.valueToken.innerValue.offset; + } + attributes.add(new TextAttribute(_attr.name, _attr.nameOffset, value, + valueOffset, dartParser.findMustaches(value, valueOffset))); + } + }); + + element.properties.forEach((property) { + if (property.name.startsWith("class")) { + attributes.add(_convertExpressionBoundAttribute( + property, "[class.", "]", ExpressionBoundType.clazz)); + } else if (property.name.startsWith("attr")) { + attributes.add(_convertExpressionBoundAttribute( + property, "[attr.", "]", ExpressionBoundType.attr)); + } else if (property.name.startsWith("style")) { + attributes.add(_convertExpressionBoundAttribute( + property, "[style.", "]", ExpressionBoundType.style)); + } else { + attributes.add(_convertExpressionBoundAttribute( + property, "[", "]", ExpressionBoundType.input)); + } + }); + + element.references.forEach((reference) { + String value; + int valueOffset; + ParsedReferenceAst _attr = reference as ParsedReferenceAst; + if (_attr.valueToken != null) { + value = _attr.valueToken.innerValue.lexeme; + valueOffset = _attr.valueToken.innerValue.offset; + } + attributes.add(new TextAttribute( + _attr.prefixToken.lexeme + _attr.nameToken.lexeme, + _attr.prefixToken.offset, + value, + valueOffset, + dartParser.findMustaches(value, valueOffset))); + }); + } + return attributes; + } + TemplateAttribute _convertTemplateAttribute(TemplateAst ast) { String name; int nameOffset; diff --git a/analyzer_plugin/lib/src/resolver.dart b/analyzer_plugin/lib/src/resolver.dart index f4b86966..067ff98e 100644 --- a/analyzer_plugin/lib/src/resolver.dart +++ b/analyzer_plugin/lib/src/resolver.dart @@ -838,21 +838,12 @@ class NgContentRecorder extends AngularScopeVisitor { List selectorAttrs = element.attributes.where((a) => a.name == 'select'); - for (NodeInfo child in element.childNodes) { - if (!child.isSynthetic) { - errorReporter.reportErrorForOffset( - AngularWarningCode.NG_CONTENT_MUST_BE_EMPTY, - element.openingSpan.offset, - element.openingSpan.length); - } - } - if (selectorAttrs.length == 0) { template.ngContents.add(new NgContent(element.offset, element.length)); return; } - // We don't actually check if selectors.length > 2, because the html parser + // We don't actually check if selectors.length > 2, because the parser // reports that. try { AttributeInfo selectorAttr = selectorAttrs.first; diff --git a/analyzer_plugin/lib/src/tasks.dart b/analyzer_plugin/lib/src/tasks.dart index 84b2447f..8c53ff45 100644 --- a/analyzer_plugin/lib/src/tasks.dart +++ b/analyzer_plugin/lib/src/tasks.dart @@ -338,7 +338,7 @@ abstract class ParseHtmlMixin implements AnalysisTask { desugar: false, exceptionHandler: exceptionHandler, ); - } catch (e, stack){ + } catch (e, stack) { print(stack); } diff --git a/analyzer_plugin/lib/tasks.dart b/analyzer_plugin/lib/tasks.dart index 70320059..64b29839 100644 --- a/analyzer_plugin/lib/tasks.dart +++ b/analyzer_plugin/lib/tasks.dart @@ -308,16 +308,6 @@ class AngularWarningCode extends ErrorCode { "The content does not match any transclusion selectors of the" + " surrounding component"); - /** - * An error code indicating that an tag had content, which is not - * allowed. - */ - static const AngularWarningCode NG_CONTENT_MUST_BE_EMPTY = - const AngularWarningCode( - 'NG_CONTENT_MUST_BE_EMPTY', - "Nothing is allowed inside an tag, as it will be" + - " replaced"); - /** * Initialize a newly created error code to have the given [name]. * The message associated with the error will be created from the given diff --git a/analyzer_plugin/test/abstract_angular.dart b/analyzer_plugin/test/abstract_angular.dart index 7ebfeb13..40e35ae8 100644 --- a/analyzer_plugin/test/abstract_angular.dart +++ b/analyzer_plugin/test/abstract_angular.dart @@ -327,7 +327,8 @@ class NgFor { * occurred during angular analysis. */ // TODO: Max: remove debug flag. - void assertMultipleErrorsExplicit(List expectedErrors, {bool debug: false}) { + void assertMultipleErrorsExplicit(List expectedErrors, + {bool debug: false}) { var realErrors = errorListener.errors; if (debug) { realErrors.forEach((error) { @@ -341,7 +342,8 @@ class NgFor { } expectedErrors.forEach((expectedError) { expect(realErrors.contains(expectedError), true, - reason: 'Expected error code ${expectedError.errorCode} never occurs at ' + reason: + 'Expected error code ${expectedError.errorCode} never occurs at ' 'location ${expectedError.offset} of length ${expectedError.length}.'); }); expect(realErrors.length, expectedErrors.length, diff --git a/analyzer_plugin/test/converter_test.dart b/analyzer_plugin/test/converter_test.dart index 44a0c892..86b20190 100644 --- a/analyzer_plugin/test/converter_test.dart +++ b/analyzer_plugin/test/converter_test.dart @@ -27,26 +27,21 @@ class ConverterTest extends AbstractAngularTest { void test_scratch() { _addDartSource(r''' -@Component( - selector: 'name-panel', - inputs: const ['aaa', 'bbb', 'ccc']) -@View(template: r"
AAA
") -class NamePanel { - int aaa; - int bbb; - int ccc; -} @Component(selector: 'test-panel') -@View(templateUrl: 'test_panel.html', directives: const [NamePanel]) -class TestPanel {} +@View(templateUrl: 'test_panel.html') +class TestPanel { +} '''); - _addHtmlSource(r""" - -"""); + String code = r""" +
+ +
+ """; + _addHtmlSource(code); + computeResult( + new LibrarySpecificUnit(dartSource, dartSource), ANGULAR_ASTS); _resolveSingleTemplate(dartSource); - ResolvedRange resolvedRange1 = _findResolvedRange("bbb]="); - ResolvedRange resolvedRange2 = _findResolvedRange('ccc='); - print(resolvedRange2); + expect(template.ngContents, hasLength(0)); } void _addDartSource(String code) { diff --git a/analyzer_plugin/test/resolver_test.dart b/analyzer_plugin/test/resolver_test.dart index ded4f40d..8c287705 100644 --- a/analyzer_plugin/test/resolver_test.dart +++ b/analyzer_plugin/test/resolver_test.dart @@ -1021,7 +1021,8 @@ class TestPanel { assertMultipleErrorsExplicit([ new AnalysisError( htmlSource, 29, 0, AngularWarningCode.NONEXIST_INPUT_BOUND, ['']), - new AnalysisError(htmlSource, 29, 1, NgParserWarningCode.AFTER_DECORATOR_NEED_WHITESPACE), + new AnalysisError(htmlSource, 29, 1, + NgParserWarningCode.AFTER_DECORATOR_NEED_WHITESPACE), new AnalysisError(htmlSource, 6, 14, NgParserWarningCode.SUFFIX_PROPERTY), ]); } @@ -1055,11 +1056,14 @@ class TestPanel { _addHtmlSource(code); _resolveSingleTemplate(dartSource); assertMultipleErrorsExplicit([ - new AnalysisError(htmlSource, 29, 0, AngularWarningCode.NONEXIST_INPUT_BOUND, ['']), - new AnalysisError(htmlSource, 29, 1, NgParserWarningCode.AFTER_DECORATOR_NEED_WHITESPACE), - new AnalysisError(htmlSource, 19, 1, NgParserWarningCode.UNEXPECTED_TOKEN), + new AnalysisError( + htmlSource, 29, 0, AngularWarningCode.NONEXIST_INPUT_BOUND, ['']), + new AnalysisError(htmlSource, 29, 1, + NgParserWarningCode.AFTER_DECORATOR_NEED_WHITESPACE), + new AnalysisError( + htmlSource, 19, 1, NgParserWarningCode.UNEXPECTED_TOKEN), new AnalysisError(htmlSource, 6, 14, NgParserWarningCode.SUFFIX_PROPERTY), - ]); + ]); } void test_expression_styleBinding_withUnit_invalidUnitName() { @@ -1075,9 +1079,12 @@ class TestPanel { _addHtmlSource(code); _resolveSingleTemplate(dartSource); assertMultipleErrorsExplicit([ - new AnalysisError(htmlSource, 30, 0, AngularWarningCode.NONEXIST_INPUT_BOUND, ['']), - new AnalysisError(htmlSource, 30, 1, NgParserWarningCode.AFTER_DECORATOR_NEED_WHITESPACE), - new AnalysisError(htmlSource, 28, 1, NgParserWarningCode.UNEXPECTED_TOKEN), + new AnalysisError( + htmlSource, 30, 0, AngularWarningCode.NONEXIST_INPUT_BOUND, ['']), + new AnalysisError(htmlSource, 30, 1, + NgParserWarningCode.AFTER_DECORATOR_NEED_WHITESPACE), + new AnalysisError( + htmlSource, 28, 1, NgParserWarningCode.UNEXPECTED_TOKEN), new AnalysisError(htmlSource, 6, 23, NgParserWarningCode.SUFFIX_PROPERTY), ]); } @@ -1745,8 +1752,10 @@ class TestPanel { _addHtmlSource(code); _resolveSingleTemplate(dartSource); assertMultipleErrorsExplicit([ - new AnalysisError(htmlSource, 14, 1, ParserErrorCode.UNEXPECTED_TOKEN, ['}']), - new AnalysisError(htmlSource, 17, 6, StaticTypeWarningCode.UNDEFINED_GETTER, ['length', 'int']), + new AnalysisError( + htmlSource, 14, 1, ParserErrorCode.UNEXPECTED_TOKEN, ['}']), + new AnalysisError(htmlSource, 17, 6, + StaticTypeWarningCode.UNDEFINED_GETTER, ['length', 'int']), ]); } @@ -2241,29 +2250,30 @@ class TestPanel { _assertElement("item.").local.at('item ['); } - void test_ngFor_variousKinds_useLowerIdentifier() { - _addDartSource(r''' -@Component(selector: 'test-panel') -@View(templateUrl: 'test_panel.html', directives: const [NgFor]) -class TestPanel { - List items = []; -} -'''); - _addHtmlSource(r""" - -
  • - {{item2.length}} -
  • -
  • - {{item3.length}} -
  • -
    -"""); - _resolveSingleTemplate(dartSource); - errorListener.assertNoErrors(); - } + //TODO: Max: Figure out if this is deprecated; if not, what is it? +// void test_ngFor_variousKinds_useLowerIdentifier() { +// _addDartSource(r''' +//@Component(selector: 'test-panel') +//@View(templateUrl: 'test_panel.html', directives: const [NgFor]) +//class TestPanel { +// List items = []; +//} +//'''); +// _addHtmlSource(r""" +// +//
  • +// {{item2.length}} +//
  • +//
  • +// {{item3.length}} +//
  • +//
    +//"""); +// _resolveSingleTemplate(dartSource); +// errorListener.assertNoErrors(); +// } void test_ngFor_hash_instead_of_let() { _addDartSource(r''' @@ -2500,7 +2510,6 @@ class TestPanel { _addHtmlSource(code); _resolveSingleTemplate(dartSource); errorListener.assertErrorsWithCodes([ - HtmlErrorCode.PARSE_ERROR, ParserErrorCode.EXPECTED_LIST_OR_MAP_LITERAL, ParserErrorCode.EXPECTED_TOKEN, ParserErrorCode.EXPECTED_TYPE_NAME, @@ -2627,8 +2636,20 @@ class TestPanel { computeResult( new LibrarySpecificUnit(dartSource, dartSource), ANGULAR_ASTS); _resolveSingleTemplate(dartSource); - assertErrorInCodeAtPosition( - AngularWarningCode.NG_CONTENT_MUST_BE_EMPTY, code, ""); + assertMultipleErrorsExplicit([ + new AnalysisError( + htmlSource, + 8, + 12, + NgParserWarningCode.NGCONTENT_MUST_CLOSE_IMMEDIATELY, + ), + new AnalysisError( + htmlSource, + 32, + 13, + NgParserWarningCode.DANGLING_CLOSE_ELEMENT, + ), + ]); } void test_resolveTemplate_provideContentWhereInvalid() { From 743b41bc200cddfe0808c0ba3118cf125920dc4f Mon Sep 17 00:00:00 2001 From: Max Kim Date: Wed, 22 Mar 2017 11:34:28 -0700 Subject: [PATCH 12/41] All tests passing --- analyzer_plugin/lib/src/converter.dart | 58 ++++++++++--------- analyzer_plugin/test/converter_test.dart | 19 +++--- server_plugin/lib/src/completion.dart | 3 +- .../test/completion_contributor_test.dart | 2 +- 4 files changed, 43 insertions(+), 39 deletions(-) diff --git a/analyzer_plugin/lib/src/converter.dart b/analyzer_plugin/lib/src/converter.dart index 334250d6..a02a9c4c 100644 --- a/analyzer_plugin/lib/src/converter.dart +++ b/analyzer_plugin/lib/src/converter.dart @@ -34,6 +34,7 @@ class HtmlTreeConverter { HtmlTreeConverter(this.dartParser, this.templateSource, this.errorListener); ElementInfo convertFromAstList(List asts) { + // TODO: Max: Refactor or use list format? ElementInfo root; if (asts != null && asts.length == 1 && @@ -60,6 +61,9 @@ class HtmlTreeConverter { root.childNodes.add(child); } } + if (asts.isEmpty) { + root.childNodes.add(new TextInfo(0, '', root, [])); + } return root; } @@ -83,16 +87,17 @@ class HtmlTreeConverter { new SourceRange(openingSpan.offset + '<'.length, localName.length); } // Check for void element cases (has closing complement) - if (closeComponent != null) { - if (closeComponent.isSynthetic) { - closingSpan = _toSourceRange(node.endToken.end, 0); - closingNameSpan = closingSpan; - } else { - closingSpan = _toSourceRange(closeComponent.beginToken.offset, - closeComponent.endToken.end - closeComponent.beginToken.offset); - closingNameSpan = new SourceRange( - closingSpan.offset + ' items = []; } '''); - String code = r""" -
    - -
    - """; - _addHtmlSource(code); - computeResult( - new LibrarySpecificUnit(dartSource, dartSource), ANGULAR_ASTS); + _addHtmlSource(r""" + some text and {{^
    some html
    +"""); _resolveSingleTemplate(dartSource); - expect(template.ngContents, hasLength(0)); + print(template.ast.childNodes); + template.ast.childNodes.forEach((e) { + print('[${(e as TextInfo).text}]'); + }); } void _addDartSource(String code) { diff --git a/server_plugin/lib/src/completion.dart b/server_plugin/lib/src/completion.dart index 841e89e6..e92adc60 100644 --- a/server_plugin/lib/src/completion.dart +++ b/server_plugin/lib/src/completion.dart @@ -267,6 +267,8 @@ class TemplateCompleter { List suggestions = []; for (Template template in templates) { AngularAstNode target = findTarget(request.offset, template.ast); + //DEBUG SECTION + //DEBUG SECTION END target.accept(new ReplacementRangeCalculator(request)); DartSnippetExtractor extractor = new DartSnippetExtractor(); extractor.offset = request.offset; @@ -372,7 +374,6 @@ class TemplateCompleter { suggestTransclusions(target.parent, suggestions); } } - return suggestions; } diff --git a/server_plugin/test/completion_contributor_test.dart b/server_plugin/test/completion_contributor_test.dart index 4d48b4a1..481b4dd2 100644 --- a/server_plugin/test/completion_contributor_test.dart +++ b/server_plugin/test/completion_contributor_test.dart @@ -800,7 +800,7 @@ class MyComp { } '''); - addTestSource('some text and {{^
    some html
    '); + addTestSource('some text and {{^'); LibrarySpecificUnit target = new LibrarySpecificUnit(dartSource, dartSource); computeResult(target, VIEWS_WITH_HTML_TEMPLATES2); From 1e718562b2c0b2b79c832cb5c6a50c14caef5396 Mon Sep 17 00:00:00 2001 From: Max Kim Date: Thu, 23 Mar 2017 11:36:49 -0700 Subject: [PATCH 13/41] Make paths explicit --- analyzer_plugin/lib/src/tasks.dart | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/analyzer_plugin/lib/src/tasks.dart b/analyzer_plugin/lib/src/tasks.dart index 8c53ff45..e4f0967a 100644 --- a/analyzer_plugin/lib/src/tasks.dart +++ b/analyzer_plugin/lib/src/tasks.dart @@ -293,7 +293,7 @@ class AngularParseHtmlTask extends SourceBasedAnalysisTask with ParseHtmlMixin { target.source, 0, 0, ScannerErrorCode.UNABLE_GET_CONTENT, [message]) ]; } else { - parse(content, target.source.uri.path); + parse(content, target.toString()); outputs[ANGULAR_HTML_DOCUMENT] = documentAsts; outputs[ANGULAR_HTML_DOCUMENT_ERRORS] = parseErrors; } @@ -330,17 +330,12 @@ abstract class ParseHtmlMixin implements AnalysisTask { void parse(String content, String sourceUrl) { final exceptionHandler = new NgAst.RecoveringExceptionHandler(); - // TODO: Max: Remove try-catch after finished integration. - try { - documentAsts = NgAst.parse( - content, - sourceUrl: sourceUrl, - desugar: false, - exceptionHandler: exceptionHandler, - ); - } catch (e, stack) { - print(stack); - } + documentAsts = NgAst.parse( + content, + sourceUrl: sourceUrl, + desugar: false, + exceptionHandler: exceptionHandler, + ); for (NgAst.AngularParserException e in exceptionHandler.exceptions) { if (e.errorCode is NgAst.NgParserWarningCode) { @@ -1575,7 +1570,7 @@ class GetAstsForTemplatesInUnitTask extends SourceBasedAnalysisTask parse( (' ' * view.templateOffset) + view.templateText.substring(0, view.templateText.length - 1), - source.uri.path); + target.toString()); parseErrors.forEach(errorListener.onError); parseErrors.clear(); From ef23f0ef6a973837bd5731d0db021714d7a47c56 Mon Sep 17 00:00:00 2001 From: Max Kim Date: Thu, 23 Mar 2017 13:03:53 -0700 Subject: [PATCH 14/41] Remove comments --- server_plugin/lib/src/completion.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/server_plugin/lib/src/completion.dart b/server_plugin/lib/src/completion.dart index e92adc60..e7d45f06 100644 --- a/server_plugin/lib/src/completion.dart +++ b/server_plugin/lib/src/completion.dart @@ -267,8 +267,6 @@ class TemplateCompleter { List suggestions = []; for (Template template in templates) { AngularAstNode target = findTarget(request.offset, template.ast); - //DEBUG SECTION - //DEBUG SECTION END target.accept(new ReplacementRangeCalculator(request)); DartSnippetExtractor extractor = new DartSnippetExtractor(); extractor.offset = request.offset; From c0ac16e48c4176e9b6d7f5363a6a9b6947601f7a Mon Sep 17 00:00:00 2001 From: Max Kim Date: Fri, 24 Mar 2017 13:24:22 -0700 Subject: [PATCH 15/41] Attributes now sorted by offset. Improves performance in auto completion --- analyzer_plugin/lib/src/converter.dart | 1 + analyzer_plugin/lib/src/tasks.dart | 1 + analyzer_plugin/test/resolver_test.dart | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/analyzer_plugin/lib/src/converter.dart b/analyzer_plugin/lib/src/converter.dart index a02a9c4c..7da1f1b8 100644 --- a/analyzer_plugin/lib/src/converter.dart +++ b/analyzer_plugin/lib/src/converter.dart @@ -71,6 +71,7 @@ class HtmlTreeConverter { if (node is ElementAst) { String localName = node.name; List attributes = _convertAttributes(node); + attributes.sort((a,b)=>a.offset.compareTo(b.offset)); final closeComponent = node.closeComplement; SourceRange openingSpan; SourceRange openingNameSpan; diff --git a/analyzer_plugin/lib/src/tasks.dart b/analyzer_plugin/lib/src/tasks.dart index e4f0967a..61bc0c65 100644 --- a/analyzer_plugin/lib/src/tasks.dart +++ b/analyzer_plugin/lib/src/tasks.dart @@ -334,6 +334,7 @@ abstract class ParseHtmlMixin implements AnalysisTask { content, sourceUrl: sourceUrl, desugar: false, + parseExpressions: false, exceptionHandler: exceptionHandler, ); diff --git a/analyzer_plugin/test/resolver_test.dart b/analyzer_plugin/test/resolver_test.dart index 8c287705..b5c3dd96 100644 --- a/analyzer_plugin/test/resolver_test.dart +++ b/analyzer_plugin/test/resolver_test.dart @@ -1833,8 +1833,8 @@ class TestPanel {} expect(search.element.boundDirectives, hasLength(1)); DirectiveBinding boundDirective = search.element.boundDirectives.first; expect(boundDirective.outputBindings, hasLength(2)); - expect(boundDirective.outputBindings[0].boundOutput.name, 'ccc'); - expect(boundDirective.outputBindings[1].boundOutput.name, 'bbb'); + expect(boundDirective.outputBindings[0].boundOutput.name, 'bbb'); + expect(boundDirective.outputBindings[1].boundOutput.name, 'ccc'); } void test_twoWayReference() { From 5decb8018d9886670a606e6fda637d84a8a0c6be Mon Sep 17 00:00:00 2001 From: Max Kim Date: Sat, 25 Mar 2017 17:04:23 -0700 Subject: [PATCH 16/41] DOCTYPE now acceptable --- analyzer_plugin/lib/src/converter.dart | 2 +- analyzer_plugin/test/abstract_angular.dart | 1 - analyzer_plugin/test/converter_test.dart | 1 - analyzer_plugin/test/resolver_test.dart | 1 - analyzer_plugin/test/tasks_test.dart | 15 ++------------- 5 files changed, 3 insertions(+), 17 deletions(-) diff --git a/analyzer_plugin/lib/src/converter.dart b/analyzer_plugin/lib/src/converter.dart index 7da1f1b8..70ce5aec 100644 --- a/analyzer_plugin/lib/src/converter.dart +++ b/analyzer_plugin/lib/src/converter.dart @@ -71,7 +71,7 @@ class HtmlTreeConverter { if (node is ElementAst) { String localName = node.name; List attributes = _convertAttributes(node); - attributes.sort((a,b)=>a.offset.compareTo(b.offset)); + attributes.sort((a, b) => a.offset.compareTo(b.offset)); final closeComponent = node.closeComplement; SourceRange openingSpan; SourceRange openingNameSpan; diff --git a/analyzer_plugin/test/abstract_angular.dart b/analyzer_plugin/test/abstract_angular.dart index 40e35ae8..501935ac 100644 --- a/analyzer_plugin/test/abstract_angular.dart +++ b/analyzer_plugin/test/abstract_angular.dart @@ -19,7 +19,6 @@ import 'package:angular_analyzer_plugin/src/selector.dart'; import 'package:angular_analyzer_plugin/src/tasks.dart'; import 'package:plugin/manager.dart'; import 'package:plugin/plugin.dart'; -import 'package:tuple/tuple.dart'; import 'package:unittest/unittest.dart'; import 'mock_sdk.dart'; diff --git a/analyzer_plugin/test/converter_test.dart b/analyzer_plugin/test/converter_test.dart index 30ea25bd..20cdb518 100644 --- a/analyzer_plugin/test/converter_test.dart +++ b/analyzer_plugin/test/converter_test.dart @@ -1,5 +1,4 @@ import 'package:analyzer/src/generated/source.dart'; -import 'package:analyzer/task/dart.dart'; import 'package:angular_analyzer_plugin/src/model.dart'; import 'package:angular_analyzer_plugin/src/tasks.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; diff --git a/analyzer_plugin/test/resolver_test.dart b/analyzer_plugin/test/resolver_test.dart index b5c3dd96..db9144b4 100644 --- a/analyzer_plugin/test/resolver_test.dart +++ b/analyzer_plugin/test/resolver_test.dart @@ -12,7 +12,6 @@ import 'package:angular_analyzer_plugin/src/tasks.dart'; import 'package:angular_analyzer_plugin/tasks.dart'; import 'package:angular_ast/angular_ast.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; -import 'package:tuple/tuple.dart'; import 'package:unittest/unittest.dart'; import 'abstract_angular.dart'; diff --git a/analyzer_plugin/test/tasks_test.dart b/analyzer_plugin/test/tasks_test.dart index 4c5061ad..9e9cc6d8 100644 --- a/analyzer_plugin/test/tasks_test.dart +++ b/analyzer_plugin/test/tasks_test.dart @@ -76,19 +76,8 @@ class AngularParseHtmlTaskTest extends AbstractAngularTest { } test_perform() { - //TODO: Max: revert back once DOCTYPE is implement -// String code = r''' -// -// -// -// test page -// -// -//

    Test

    -// -// -// '''; String code = r''' + test page @@ -108,7 +97,7 @@ class AngularParseHtmlTaskTest extends AbstractAngularTest { expect(asts, isNotNull); expect(asts.isEmpty, false); // verify that attributes are not lower-cased - ElementAst element = asts[0].childNodes[3].childNodes[1]; + ElementAst element = asts[1].childNodes[3].childNodes[1]; expect(element.attributes.length, 1); expect(element.attributes[0].name, 'myAttr'); expect(element.attributes[0].value, 'my value'); From bd6987c2add1b48e7a63bcf193b7adeaf80329e2 Mon Sep 17 00:00:00 2001 From: Max Kim Date: Sun, 26 Mar 2017 20:10:03 -0700 Subject: [PATCH 17/41] Created separate Ast for top level document container --- analyzer_plugin/lib/ast.dart | 36 ++++++++++++++++++++++ analyzer_plugin/lib/src/converter.dart | 34 +++++--------------- analyzer_plugin/lib/src/resolver.dart | 13 ++++++++ analyzer_plugin/lib/src/tasks.dart | 1 - analyzer_plugin/test/abstract_angular.dart | 13 +------- analyzer_plugin/test/converter_test.dart | 30 +++++++++--------- server_plugin/lib/src/completion.dart | 8 +++++ 7 files changed, 81 insertions(+), 54 deletions(-) diff --git a/analyzer_plugin/lib/ast.dart b/analyzer_plugin/lib/ast.dart index 7cb8d7cd..61dabd1d 100644 --- a/analyzer_plugin/lib/ast.dart +++ b/analyzer_plugin/lib/ast.dart @@ -16,6 +16,12 @@ abstract class AngularAstNode { } abstract class AngularAstVisitor { + void visitDocumentInfo(DocumentInfo document) { + for (AngularAstNode child in document.childNodes) { + child.accept(this); + } + } + void visitMustache(Mustache mustache) {} void visitTextAttr(TextAttribute textAttr) => _visitAllChildren(textAttr); @@ -287,6 +293,36 @@ class TextInfo extends NodeInfo { void accept(AngularAstVisitor visitor) => visitor.visitTextInfo(this); } +/** + * A wrapper for a given HTML document or + * dart-angular inline HTML template. + */ +class DocumentInfo extends ElementInfo { + factory DocumentInfo() = DocumentInfo._; + + DocumentInfo._() + : super( + '', + new SourceRange(0, 0), + new SourceRange(0, 0), + new SourceRange(0, 0), + new SourceRange(0, 0), + false, + [], + null, + null, + ); + + @override + bool get isSynthetic => false; + + @override + List get children => childNodes; + + @override + void accept(AngularAstVisitor visitor) => visitor.visitDocumentInfo(this); +} + /** * An element in an HTML tree. */ diff --git a/analyzer_plugin/lib/src/converter.dart b/analyzer_plugin/lib/src/converter.dart index 70ce5aec..a94d804b 100644 --- a/analyzer_plugin/lib/src/converter.dart +++ b/analyzer_plugin/lib/src/converter.dart @@ -33,37 +33,17 @@ class HtmlTreeConverter { HtmlTreeConverter(this.dartParser, this.templateSource, this.errorListener); - ElementInfo convertFromAstList(List asts) { - // TODO: Max: Refactor or use list format? - ElementInfo root; - if (asts != null && - asts.length == 1 && - asts[0] is ElementAst && - (asts[0] as ElementAst).name == 'html') { - root = convert(asts[0]); - return root; - } else { - root = new ElementInfo( - 'html', - new SourceRange(0, 0), - new SourceRange(0, 0), - new SourceRange(0, 0), - new SourceRange(0, 0), - false, - [], - null, - null, - ); + DocumentInfo convertFromAstList(List asts) { + DocumentInfo root = new DocumentInfo(); + if (asts.isEmpty) { + root.childNodes.add(new TextInfo(0, '', root, [])); } for (StandaloneTemplateAst node in asts) { - NodeInfo child = convert(node, parent: root); - if (child != null) { - root.childNodes.add(child); + var convertedNode = convert(node, parent: root); + if (convertedNode != null) { + root.childNodes.add(convertedNode); } } - if (asts.isEmpty) { - root.childNodes.add(new TextInfo(0, '', root, [])); - } return root; } diff --git a/analyzer_plugin/lib/src/resolver.dart b/analyzer_plugin/lib/src/resolver.dart index 067ff98e..fc282dd5 100644 --- a/analyzer_plugin/lib/src/resolver.dart +++ b/analyzer_plugin/lib/src/resolver.dart @@ -246,6 +246,11 @@ class _DartReferencesRecorder extends RecursiveAstVisitor { class AngularScopeVisitor extends AngularAstVisitor { bool visitingRoot = true; + void visitDocumentInfo(DocumentInfo document) { + visitingRoot = false; + visitElementInScope(document); + } + void visitElementInfo(ElementInfo element) { var isRoot = visitingRoot; visitingRoot = false; @@ -675,6 +680,14 @@ class NextTemplateElementsSearch extends AngularAstVisitor { List results = []; + @override + void visitDocumentInfo(DocumentInfo document) { + visitingRoot = false; + for (NodeInfo child in document.childNodes) { + child.accept(this); + } + } + @override void visitElementInfo(ElementInfo element) { if (element.isOrHasTemplateAttribute && !visitingRoot) { diff --git a/analyzer_plugin/lib/src/tasks.dart b/analyzer_plugin/lib/src/tasks.dart index 61bc0c65..5a4ae1c0 100644 --- a/analyzer_plugin/lib/src/tasks.dart +++ b/analyzer_plugin/lib/src/tasks.dart @@ -1806,7 +1806,6 @@ class ResolveHtmlTemplateTask extends AnalysisTask { .map((e) => new FromFilePrefixedError(view.source, e)) .toList(); } - outputs[HTML_TEMPLATE] = view.template; outputs[HTML_TEMPLATE_ERRORS] = errorList; } diff --git a/analyzer_plugin/test/abstract_angular.dart b/analyzer_plugin/test/abstract_angular.dart index 501935ac..f4249e41 100644 --- a/analyzer_plugin/test/abstract_angular.dart +++ b/analyzer_plugin/test/abstract_angular.dart @@ -326,19 +326,8 @@ class NgFor { * occurred during angular analysis. */ // TODO: Max: remove debug flag. - void assertMultipleErrorsExplicit(List expectedErrors, - {bool debug: false}) { + void assertMultipleErrorsExplicit(List expectedErrors) { var realErrors = errorListener.errors; - if (debug) { - realErrors.forEach((error) { - print(error.errorCode); - print(error.isStaticOnly); - print(error.source); - print(error.offset); - print(error.length); - print(error.message); - }); - } expectedErrors.forEach((expectedError) { expect(realErrors.contains(expectedError), true, reason: diff --git a/analyzer_plugin/test/converter_test.dart b/analyzer_plugin/test/converter_test.dart index 20cdb518..9a03152f 100644 --- a/analyzer_plugin/test/converter_test.dart +++ b/analyzer_plugin/test/converter_test.dart @@ -3,6 +3,7 @@ import 'package:angular_analyzer_plugin/src/model.dart'; import 'package:angular_analyzer_plugin/src/tasks.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'package:unittest/unittest.dart'; +import 'package:analyzer/task/dart.dart'; import 'package:angular_analyzer_plugin/ast.dart'; import 'abstract_angular.dart'; @@ -25,21 +26,22 @@ class ConverterTest extends AbstractAngularTest { List ranges; void test_scratch() { - _addDartSource(r''' -@Component(selector: 'test-panel') -@View(templateUrl: 'test_panel.html', directives: const [NgFor]) -class TestPanel { - List items = []; + String code = r''' +import '/angular2/angular2.dart'; + +@Component(selector: 'my-aaa', + template: """ + +""") +class ComponentA { } -'''); - _addHtmlSource(r""" - some text and {{^
    some html
    -"""); - _resolveSingleTemplate(dartSource); - print(template.ast.childNodes); - template.ast.childNodes.forEach((e) { - print('[${(e as TextInfo).text}]'); - }); +'''; + Source source = newSource('/test.dart', code); + LibrarySpecificUnit target = new LibrarySpecificUnit(source, source); + computeResult(target, VIEWS2); + computeResult(target, ANGULAR_ASTS_ERRORS); + print(outputs); + //expect(task, new isInstanceOf()); } void _addDartSource(String code) { diff --git a/server_plugin/lib/src/completion.dart b/server_plugin/lib/src/completion.dart index e7d45f06..1c2511d5 100644 --- a/server_plugin/lib/src/completion.dart +++ b/server_plugin/lib/src/completion.dart @@ -61,6 +61,9 @@ class DartSnippetExtractor extends AngularAstVisitor { AstNode dartSnippet = null; int offset; + @override + visitDocumentInfo(DocumentInfo document) {} + // don't recurse, findTarget already did that @override visitElementInfo(ElementInfo element) {} @@ -126,6 +129,8 @@ class LocalVariablesExtractor extends AngularAstVisitor { // don't recurse, findTarget already did that @override + visitDocumentInfo(DocumentInfo document) {} + @override visitElementInfo(ElementInfo element) {} @override visitTextAttr(TextAttribute attr) {} @@ -151,6 +156,9 @@ class ReplacementRangeCalculator extends AngularAstVisitor { ReplacementRangeCalculator(this.request); + @override + visitDocumentInfo(DocumentInfo document) {} + // don't recurse, findTarget already did that @override visitElementInfo(ElementInfo element) { From c7fa463d589dea9ff01026ef22e75d3fb409e37e Mon Sep 17 00:00:00 2001 From: Max Kim Date: Wed, 29 Mar 2017 10:23:09 -0700 Subject: [PATCH 18/41] Refactored primary function in completion.dart --- server_plugin/lib/src/completion.dart | 97 +++++++++++++-------------- 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/server_plugin/lib/src/completion.dart b/server_plugin/lib/src/completion.dart index 1c2511d5..32eb12fa 100644 --- a/server_plugin/lib/src/completion.dart +++ b/server_plugin/lib/src/completion.dart @@ -272,88 +272,83 @@ class TemplateCompleter { List