Skip to content
This repository has been archived by the owner on Sep 24, 2021. It is now read-only.

Initial commit of Analyzer-based Linter. Ported over first linter 'unbalanced-delimiters' #1

Closed
wants to merge 14 commits into from

Conversation

usergenic
Copy link
Contributor

@usergenic usergenic commented Oct 6, 2016

This change is Reviewable

public async lint(files: string[]): Promise<Warning[]> {
let warnings: Warning[] = [];
for (const file of files) {
const document: Document = await this.analyzer.analyze(file);
Copy link
Contributor

@rictic rictic Oct 6, 2016

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;
}

Copy link
Contributor Author

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully this is temporary?

Copy link
Contributor

@rictic rictic Oct 10, 2016

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.

Copy link
Contributor Author

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.
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

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.
Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Contributor

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')) {
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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);
}
}
Copy link
Contributor

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.

Copy link
Contributor Author

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);
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. Converting.

Copy link
Contributor Author

@usergenic usergenic Oct 11, 2016

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) => {
Copy link
Contributor

@rictic rictic Oct 6, 2016

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)

Copy link
Contributor

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.

Copy link
Contributor Author

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, {{}, {}}, [[], []]
Copy link
Contributor

@rictic rictic Oct 6, 2016

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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}`,
Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Contributor

@justinfagnani justinfagnani left a 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",
Copy link
Contributor

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

Copy link
Contributor Author

@usergenic usergenic Oct 11, 2016

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';
Copy link
Contributor

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.

Copy link
Contributor Author

@usergenic usergenic Oct 11, 2016

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);
Copy link
Contributor

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)) {
Copy link
Contributor

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?

Copy link
Contributor Author

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) => {
Copy link
Contributor

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;
Copy link
Contributor

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 {
Copy link
Contributor

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.
Copy link
Contributor

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',
Copy link
Contributor

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] !== '$') {
Copy link
Contributor

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;
Copy link
Contributor

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">
Copy link
Contributor

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},
Copy link
Contributor

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>
Copy link
Contributor

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
Copy link
Contributor

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?

@rictic
Copy link
Contributor

rictic commented Oct 12, 2016

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!

rictic pushed a commit that referenced this pull request Dec 21, 2016
rictic added a commit that referenced this pull request Dec 22, 2016
… 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.
@googlebot
Copy link

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 cla/google commit status will not change from this state. It's up to you to confirm consent of the commit author(s) and merge this pull request when appropriate.

@rictic
Copy link
Contributor

rictic commented Dec 22, 2016

(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.)

@rictic
Copy link
Contributor

rictic commented Dec 22, 2016

Also be quiet googlebot.

@usergenic
Copy link
Contributor Author

closing this PR because @rictic's work obviates this bit-o scaffolding

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants