EmmanuelDemey/eslint-plugin-angular

Don't lint non-angular files

ptarjan opened this issue · 18 comments

I tried using your plugin, but part of my repo is angular and another part is just normal JS files. Can you only apply your rules if there is a angular.module() in the chain or something smart?

What kind of false positives do you have?

Tons. Here are a few samples:

   4:1   error  You should use the $window service instead of the default window object      angular/window-service
  114:7   error  You should use the "error" method of the AngularJS Service $log instead of the console object  angular/log
  19:11  error  You should use the toJson method instead of JSON.stringify                                   angular/json-functions
   674:26  error    You should not use directly the "undefined" keyword. Prefer angular.isUndefined or angular.isDefined  angular/definedundefined
  244:33  warning  You should use angular.element instead of the jQuery $ object                                angular/angularelement
  25:15  error    You should use the angular.isArray method                      angular/typecheck-array

It's not possible to determine which files you want to lint. This is a personal preference.

If your code base separates the angular part and the non-angular part, you could implement this yourself though.

Assuming you have the following layout

app/
 |- angular-app/
 |   `- .eslintrc
 |- non-angular/
 |   `- .eslintrc
 `- .eslintrc

Where app/.eslintrc contains your common settings, app/non-angular/.eslintrc contains your non-angular specific rules and app/angular-app/.eslintrc contains:

plugins:
  - angular

I use a similar approach to distinguish between the rules for my configuration files (gulpfile, Karma configuration, etc.) and my AngularJS source files.

I often intermix pure JS modules in the same dir as my angular services or controllers.

Could this plugin just make sure there is a user of angular.module in the file before it reports anything?

I think it is not possible to detect code inside angular.module, because developers can use AMD or CJS module.
We can define the implementation of the AngularJS component in a file, and include it in the AngularJS architecture (via filter/directive/service/controller..) in another file.
In the first file, we do not have any angular.module call, so it is not possible to detect AngularJS code in this way.

Can you only lint inside a angular.controller and angular.service etc.?

Sent from my iPhone

On Oct 8, 2015, at 2:29 AM, Emmanuel DEMEY notifications@github.com wrote:

I think it is not possible to detect code inside angular.module, because developers can use AMD or CJS module.
We can define the implementation of the AngularJS component in a file, and include it in the AngularJS architecture (via filter/directive/service/controller..) in another file.
In the first file, we do not have any angular.module call, so it is not possible to detect AngularJS code in this way.


Reply to this email directly or view it on GitHub.

As I tried to explain I think it is not possible. For exemple, imagine I have an ES2015 service class in a service.js file :

export class Service {}

And in other file, I register this service into Angular via an application.js file :

import {Service} from 'myModule'
angular.module('Service', Service)

How can we detect, in the service.js, that the class is in fact an AngularJS Service component ? I think it is impossible.

Manu

I agree. JavaScript is Turing complete so you can't know if something will be registered as a controller unless you execute it. But as with all static analysis, you do your best. My vote is to err on the side of false negatives not false positives for a linter. So only yell if you are sure it is a controller or service or whatever and the slowly get smarter about detecting them.

Sent from my iPhone

On Oct 11, 2015, at 6:35 AM, Emmanuel DEMEY notifications@github.com wrote:

As I tried to explain I think it is not possible. For exemple, imagine I have an ES2015 service class in a service.js file :

export class Service {}
And in other file, I register this service into Angular via an application.js file :

import {Service} from 'myModule'
angular.module('Service', Service)
How can we detect, in the service.js, that the class is in fact an AngularJS Service component ? I think it is impossible.

Manu


Reply to this email directly or view it on GitHub.

We could add a global setting and turn off the rules based on some heuristics. The detection might be incomplete, but it's a tradeoff.

"settings": {
  "angular/detect-angular-context": ["module-setter"]
}

It think the simplest way it to look for a module getter or setter and then use the rules for the complete file. It might be also possible to check all parent nodes for a certain node, but I pretty sure this will cause performance issues. A file based approach is simpler.

I think it is a per rule setting which ones should be enabled.

I agree with @ptrarjan that the following errors should only be detected when inside of an AngularJS component:

   4:1   error  You should use the $window service instead of the default window object      angular/window-service
  114:7   error  You should use the "error" method of the AngularJS Service $log instead of the console object  angular/log

However, these rules could still apply when used outside of an AngularJS component. This is purely a preference. These rules can be disabled using /* eslint-disable angular/<rule_name> */.

  19:11  error  You should use the toJson method instead of JSON.stringify                                   angular/json-functions
   674:26  error    You should not use directly the "undefined" keyword. Prefer angular.isUndefined or angular.isDefined  angular/definedundefined
  244:33  warning  You should use angular.element instead of the jQuery $ object                                angular/angularelement
  25:15  error    You should use the angular.isArray method                      angular/typecheck-array

I personally don't have experience yet using ES6 specific syntax such as classes combined with AngularJS. ngAnnotate has support for ES6 and TypeScript. I think the 'ngInject'; annotation can be used to detect services or controllers from classes.

Another solution could be a disabling of the entire plugin from within a file via inline comments, but it seems to be not supported by eslint yet (eslint/eslint#3419, eslint/eslint#2118)

... or the angular components could be ignored using some filename conventions.

"settings": {
    "angular/file-name-pattern": "/\w+\.(module|service|controller|directive|filter)\.js/"
}

Another suggestion. We provide different configurable heuristics for detecting angular files.

"settings": {
    "angular/only-lint-matching-files": {
        // configured heuristic
   }
}

Ideas for such options...

  • "code-patterns": ["module-setters", "module-getters", "angular-object"]
    • ["module-setters"] - presence of a module setter
    • ["module-getters"] - presence of a module getter
    • ["angular-object"] - any use of the angular object
  • "file-name-patter": ["\.(module|service|controller|directive|filter)\.js$"]

If we find better ways to detect angular files we can easily add an options to this list.

The "code-pattern" option will cover the usecase of @ptarjan but it is also possible to write angular components without any reference to the framework with the "file-name-pattern" option.

I've added a proof of concept to my fork (here), which kinda allows to control whether or not plugin rules will be executed. It looks hacky, but might work. Basically it uses local variable to block rule execution from within a plugin, it resets the variable after each file ends and updates variable value when rule is enabled again. The following rule should block all angular rules for for current file:

/* eslint "angular/disable-plugin": 2 */

It does not work in alignment with ESLint API and may lead to bugs in plugin and maybe even ESLint, I agree if it won't end up in the plugin, but is one of the alternatives.

@mradionov Nice. Your proof of concept seems to work, but as you already said, it is a little hacky and I fear it may be hard to predict side effects.

I took a part of your approach to disabled rules by a filename pattern as another proof of concept.

If anybody is interested, I wrote a plugin for ESLint, which allows to disable plugins per file via inline comments https://github.com/mradionov/eslint-plugin-disable

@mradionov Nice! I think this is the best solution by far.

I'm closing this issue, because I think this fixes it. I don't think it should be part of an AngularJS plugin to handle this.

If anyone disagrees, just reopen it.

@remcohaszing I also like the solution and we should mention it in the docs, but I also think that some convention based approach is necessary, if a project has a lot on non angular resources.