-
Notifications
You must be signed in to change notification settings - Fork 11
Initial commit of Analyzer-based Linter. Ported over first linter 'unbalanced-delimiters' #1
Conversation
…balanced-delimiters'
public async lint(files: string[]): Promise<Warning[]> { | ||
let warnings: Warning[] = []; | ||
for (const file of files) { | ||
const document: Document = await this.analyzer.analyze(file); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To catch stuff like syntax errors that stop us from creating a Document object, something like this is needed:
let document: Document;
try {
document = await this.analyzer.analyze(file);
} catch (error) {
if (error instanceof WarningCarryingException) {
warnings.push(error.warning);
continue;
}
throw error;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I saw that in the linter demo code-- I'll yoink it for here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hopefully this is temporary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the alternative? We've asked the analyzer to analyze a file, but it has a syntax error.
I guess we could return some sort of mostly empty Document object that has the parse error as a warning on it, but for most use cases I think that's a foot gun.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a catch now for the specific Error
subclass WarningCarryingException
as in the demo linter file in analyzer.
|
||
/** | ||
* Given an array of filenames, lint the files and return an array of all | ||
* warnings produced evaluating the linter rules. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be worth noting that the files need to be ones that the analyzer
instance can resolve?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double check that this change made it in.
import {Warning} from 'polymer-analyzer/lib/warning/warning'; | ||
|
||
// TODO(usergenic): Linter rules should have a standard interface for | ||
// configuring Warning severity and for setting their options. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect we'll want to do warning severity filtering and modification in a central place, downstream of individual rules
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That may be true. Rule configurability isn't an issue yet, just something I anticipate. Having a standard interface in constructor will be useful to configure Rule where they have configurability, like keyword blacklists or whatever. This also would allow polymer-linter.json or poylmer.json linter stanza to include per-rule config bits that are handed to the constructors in an obvious way.
Like, maybe instantiating the linter by way of a linter config would be similar to the way we do properties in Polymer. A rule class name on the left and in the typical case, the value on the right is just true
or false
and in the configurable case its an object i.e. { whitelist: ['h1','p','em','strong'] }
Anyways that's why I made them classes with a standard check
method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definite +1 to making them configurable classes. I think we're going to want to add code
and description
properties to the Rule classes too.
} | ||
|
||
// Address binding expressions only for polymer elements. | ||
for (const element of document.getByKind('polymer-element')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to structure this rule with a getDataBindingExpressions
method? That'd be really broadly useful.
Hm, I guess not, as we're looking for stuff that's only almost legal data binding expressions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a tricky one. If we extend analyzer to include binding expressions as features, it seems like it maybe shouldn't return binding expressions which are not properly formed, since that would be confusing. It totally depends on the way Analyzer treats returning features from scanners that are improperly formed or invalid in some way as a matter of convention.
Do you have any thoughts on whether Analyzer would report bad binding expressions as features?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chatted out in person. I think consensus was to add this logic into the analyzer at some point, but it's fine to land it here for now.
parsedHtml, element)) { | ||
warnings.push(warning); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also check inside dom-bind templates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great point- I totally forgot dom-bind was a thing. Adding.
return warnings; | ||
} | ||
|
||
const template = dom5.query(element.domModule, matchers.isTemplate); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An element's dom-module could be in another file. I'd recommend doing a getByKind('dom-module')
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. Converting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since I also had to support dom-bind
templates, I dropped the polymer element stuff and just search for <template>
inside dom-modules
and <template is="dom-bind">
without using the analyzed feature for now. Will migrate to feature when dom-bind
templates are treated as such.
const templateContent = | ||
parse5.treeAdapters.default.getTemplateContent(template); | ||
|
||
dom5.nodeWalkAll(templateContent, (node: parse5.ASTNode) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want to recurse into inner template contents as well. (or maybe just dom-if
and dom-repeat
? but with that changing in 2.0, probably easier to just recurse into all template elements)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in theory one can vend other directive-style templates, though no one really does. So traversing into all templates is the easy and slightly more correct bet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recursing without discriminating. Added a comment to point out there's no condition applied and one may be considered in the future.
} | ||
|
||
private _extractBadBindingExpression(text: string): string|undefined { | ||
// 4 cases, {{}, {}}, [[], []] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there are just four cases, could we check for them individually? It would make for more legible code (and I say this as a generally regex-loving weirdo), and we could provide better warning messages. For me, I think the ideal message here would be:
Invalid polymer expression delimiters. You put '{{}' are you missing a closing }
I think it's worthwhile to say that they're polymer expression delimiters (and not like, a missing closing paren inside the polymer expression), and to say what we saw and how to fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mostly copied and pasted this bit from polylint and hadn't really even thought of improving the matcher given there were 13 linters left to port over, but you make a good point about maintainability + legibility + much better result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Step 1 complete: I made the messages nicer. I have not made bad binding expression match code nicer yet.
code: 'unbalanced-delimiters', | ||
message: `Expression ${attr | ||
.value | ||
} has unbalanced delimiters in attribute ${attr.name}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally I'd leave out information in the message that's expressed well in the sourceRange
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good rule to live by with these. Unredundantizing.
…ule now that its not needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Man, I really have to get better about submitting my review comments!
@@ -0,0 +1,44 @@ | |||
{ | |||
"name": "polymer-linter", | |||
"version": "1.0.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's start with a pre-release tag: 1.0.0-alpha.1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
* http://polymer.github.io/PATENTS.txt | ||
*/ | ||
|
||
'use strict'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not necessary, tsc adds this for us.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh! Good to know. Removed from all the things.
public async lint(files: string[]): Promise<Warning[]> { | ||
let warnings: Warning[] = []; | ||
for (const file of files) { | ||
const document: Document = await this.analyzer.analyze(file); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hopefully this is temporary?
console.assert(element instanceof PolymerElement); | ||
|
||
for (const warning of this._warningsForPolymerElement( | ||
parsedHtml, element)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
really funky wrapping here, can you add a variable to hold the warnings before the loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Take it up with clang-format! But okay :)
const templateContent = | ||
parse5.treeAdapters.default.getTemplateContent(template); | ||
|
||
dom5.nodeWalkAll(templateContent, (node: parse5.ASTNode) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in theory one can vend other directive-style templates, though no one really does. So traversing into all templates is the easy and slightly more correct bet.
function primaryName(expression: string): string { | ||
// TODO(usergenic): Remove this commented out section copied in from polylint | ||
// if (expression.name) { | ||
// expression = expression.name; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this was sometimes an estree.Identifier
, I say aggressively delete.
* @param {string} type One of 'computed', 'literal', or 'reference' | ||
* @param {string} raw The unparsed expression | ||
*/ | ||
export class ParsedExpression { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recommend instead making this:
type ParsedExpression = Computed | Literal | Reference
And make each of those three types an interface with a string literal type
field. That way we can separate out the fields and type safety will help us in a lot of ways.
This is how we're typing estree, and it's paid off hugely. It lets you switch over expression.type
.. lots of stuff.
|
||
/** | ||
* Given an array of filenames, lint the files and return an array of all | ||
* warnings produced evaluating the linter rules. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double check that this change made it in.
continue; | ||
} | ||
warnings.push({ | ||
code: 'native-attribute-binding', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here and elsewhere in this file, this isn't about native
attribute binding so much as attributes which have no corresponding property.
Recommend updating the names, code, and message here.
Also, do we need to add the attr value and name here? With the new warning printer it will show you the line of code with full info.
if (nativeAttributes.has(name)) { | ||
return false; | ||
} | ||
if (name.indexOf('data-') === 0 && attr.name[name.length - 1] !== '$') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we checking whether attr.name ends with $
here? Shouldn't it be checked elsewhere already?
|
||
private _isBindingExpression(expression: string): boolean { | ||
const bindingMatch = expressionParser.extractBindingExpression(expression); | ||
return !!bindingMatch || false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK !!foo || false
is equal to !!foo
for all values of foo
.
@@ -0,0 +1,17 @@ | |||
<link rel="import" href="../../polymer/polymer.html"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this used in a later commit?
'The expression [[myVars]] bound to attribute \'class\' should use $= instead of =', | ||
severity: Severity.ERROR, | ||
sourceRange: { | ||
end: {column: 28, line: 11}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recommend using the WarningPrinter#printUnderlinedText() trick I used in my analyzer PR recently to test these source ranges. Makes them more maintainable and more easily reviewed.
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt | ||
--> | ||
<dom-module id="bind-to-data"> | ||
<template> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider putting this into a dom-bind instead
@@ -0,0 +1,12 @@ | |||
# JSConformance sample files |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, was this actually implemented in original polylint?
This is good work, but would it be possible to break it up into separate PRs? You're adding code at a faster rate than we can get it reviewed, which is a temporary situation, but it also means that nothing can land until we can catch up with your full speed run! |
… yet. Extracted by rictic from usergenic's #1 PR
… yet (#10) * Initial scaffolding for the new linter, without any rules implemented yet. Extracted by rictic from usergenic's #1 PR * Tighten down and standardize tsconfig * Improve error handling. We should never crash based on an analysis error, instead we could just turn that error into a warning on the related file. * Standardize on tslint from analyzer, + the new prefer-const * Also recover from errors thrown from lint rules.
So there's good news and bad news. 👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there. 😕 The bad news is that it appears that one or more commits were authored by someone other than the pull request submitter. We need to confirm that they're okay with their commits being contributed to this project. Please have them confirm that here in the pull request. Note to project maintainer: This is a terminal state, meaning the |
(I resolved the conflicts with master through the github UI, but it looks like it created a merge commit. Might want to rebase that out.) |
Also be quiet googlebot. |
closing this PR because @rictic's work obviates this bit-o scaffolding |
This change is![Reviewable](https://camo.githubusercontent.com/1541c4039185914e83657d3683ec25920c672c6c5c7ab4240ee7bff601adec0b/68747470733a2f2f72657669657761626c652e696f2f7265766965775f627574746f6e2e737667)