From cafe187e83d67c75b18588455d6b3bca8b48853e Mon Sep 17 00:00:00 2001 From: Dominik Ferber Date: Wed, 14 Oct 2015 20:50:24 +0200 Subject: [PATCH] feat(rule): Add rule: globals This rule defines Meteor globals. It works in conjunction with ESLints no-undef. closes #16 --- README.md | 26 +- docs/rules/globals.md | 34 +++ lib/index.js | 26 +- lib/rules/globals.js | 57 +++++ lib/util/data/globalsExportedByPackages.js | 282 +++++++++++++++++++++ package.json | 1 + tests/lib/rules/globals.js | 32 +++ 7 files changed, 441 insertions(+), 17 deletions(-) create mode 100644 docs/rules/globals.md create mode 100644 lib/rules/globals.js create mode 100644 lib/util/data/globalsExportedByPackages.js create mode 100644 tests/lib/rules/globals.js diff --git a/README.md b/README.md index 566cf632c1..e4bd719a55 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -ESLint-plugin-Meteor -=================== +# ESLint-plugin-Meteor [![Build Status][travis-image]][travis-url] [![Coverage Status][coverage-image]][coverage-url] @@ -53,12 +52,17 @@ Finally, enable all of the rules that you would like to use. ```json { "rules": { + + // Core API + "meteor/globals": 2, + "meteor/core": 2, + "meteor/pubsub": 2, + + // Best Practices + "meteor/audit-argument-checks": 2, "meteor/no-session": 2, "meteor/no-blaze-lifecycle-assignment": 2, - "meteor/no-zero-timeout": 2, - "meteor/audit-argument-checks": 2, - "meteor/core": 2, - "meteor/pubsub": 2 + "meteor/no-zero-timeout": 2 } } ``` @@ -67,12 +71,16 @@ For a more thorough introduction, read [setting up a Meteor project](docs/SETUP_ # List of supported rules +## Core API +* [globals](docs/rules/globals.md): Definitions for global Meteor variables based on environment +* [core](docs/rules/core.md): Meteor Core API +* [pubsub](docs/rules/pubsub.md): Prevent misusage of Publish and Subscribe + +## Best Practices +* [audit-argument-checks](docs/rules/audit-argument-checks.md): Enforce check on all arguments passed to methods and publish functions * [no-session](docs/rules/no-session.md): Prevent usage of Session * [no-blaze-lifecycle-assignment](docs/rules/no-blaze-lifecycle-assignment.md): Prevent deprecated template lifecycle callback assignments * [no-zero-timeout](docs/rules/no-zero-timeout.md): Prevent usage of Meteor.setTimeout with zero delay -* [audit-argument-checks](docs/rules/audit-argument-checks.md): Enforce check on all arguments passed to methods and publish functions -* [core](docs/rules/core.md): Meteor Core API -* [pubsub](docs/rules/pubsub.md): Prevent misusage of Publish and Subscribe ## To Do diff --git a/docs/rules/globals.md b/docs/rules/globals.md new file mode 100644 index 0000000000..1efefba5ff --- /dev/null +++ b/docs/rules/globals.md @@ -0,0 +1,34 @@ +# Definitions for global Meteor variables based on environment (globals) + +This rule defines global variables based on the environment the file is executed in. +This rule never emits warnings on its own. It is meant to be used with ESLint's `no-undef`. + +Do not use the Meteor environment. + +## Rule Details + +This rule is meant to be used with ESLint's `no-undef`. This rule marks Meteor's globals as defined. `no-undef` can then warn when undefined variables are used. + +The availability of properties on the defined variables is checked in other rules. + +Do not use the Meteor environment (`env: meteor` in `.eslintrc` or `$ eslint ./ --env meteor`) when using this rule. This rule exports globals based on file location, while the Meteor environment exports the globals regardless of location. This leads to ESLint thinking a global is defined when it is actually not defined (e.g. `Session` on files in `/server`). + +## Usage + +```js +{ + 'meteor/globals': 1, + 'no-undef': 2 +} +``` + +## Further Reading + +- http://eslint.org/docs/1.0.0/rules/no-undef +- [list of defined globals](lib/util/data/globalsExportedByPackages.js) + + +## Possible Improvements + +* Define only globals exported from default Meteor packages. +Add option to include other globals separately instead. diff --git a/lib/index.js b/lib/index.js index 56fc6e3d78..01d4a1317f 100755 --- a/lib/index.js +++ b/lib/index.js @@ -13,19 +13,29 @@ function unpack (rule) { module.exports = { rules: { + + // Core API + globals: unpack('./rules/globals'), + core: unpack('./rules/core'), + pubsub: unpack('./rules/pubsub'), + + // Best Practices + 'audit-argument-checks': unpack('./rules/audit-argument-checks'), 'no-session': unpack('./rules/no-session'), 'no-blaze-lifecycle-assignment': unpack('./rules/no-blaze-lifecycle-assignment'), - 'no-zero-timeout': unpack('./rules/no-zero-timeout'), - 'audit-argument-checks': unpack('./rules/audit-argument-checks'), - core: unpack('./rules/core'), - pubsub: unpack('./rules/pubsub') + 'no-zero-timeout': unpack('./rules/no-zero-timeout') }, rulesConfig: { + + // Core API + globals: 0, + core: 0, + pubsub: 0, + + // Best Practices + 'audit-argument-checks': 0, 'no-session': 0, 'no-blaze-lifecycle-assignment': 0, - 'no-zero-timeout': 0, - 'audit-argument-checks': 0, - core: 0, - pubsub: 0 + 'no-zero-timeout': 0 } } diff --git a/lib/rules/globals.js b/lib/rules/globals.js new file mode 100644 index 0000000000..99e3259ed3 --- /dev/null +++ b/lib/rules/globals.js @@ -0,0 +1,57 @@ +/** + * @fileoverview Definitions for global Meteor variables based on environment + * @author Dominik Ferber + * @copyright 2015 Dominik Ferber. All rights reserved. + * See LICENSE file in root directory for full license. + */ + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +import {Variable} from 'escope' +import globalsExportedByPackages from '../util/data/globalsExportedByPackages' + +module.exports = getMeta => context => { + + const {isLintedEnv, env} = getMeta(context.getFilename()) + + // --------------------------------------------------------------------------- + // Helpers + // --------------------------------------------------------------------------- + + function generateGlobalVariable (name, scope) { + const variable = new Variable(name, scope) + variable.eslintExplicitGlobal = false + variable.writeable = true + return variable + } + + // --------------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------------- + + if (!isLintedEnv) { + return {} + } + + return { + + Program: function () { + const globalScope = context.getScope() + const variables = globalScope.variables + + // add variables of environment to globals + Object.keys(globalsExportedByPackages).forEach(globalVar => { + const globalVarEnv = globalsExportedByPackages[globalVar] + if (globalVarEnv.indexOf(env) !== -1) { + variables.push(generateGlobalVariable(globalVar, globalScope)) + } + }) + } + + } + +} + +module.exports.schema = [] diff --git a/lib/util/data/globalsExportedByPackages.js b/lib/util/data/globalsExportedByPackages.js new file mode 100644 index 0000000000..44b25f6fbf --- /dev/null +++ b/lib/util/data/globalsExportedByPackages.js @@ -0,0 +1,282 @@ +import { + CLIENT, + SERVER, + PACKAGE, + UNIVERSAL, + PACKAGE_CONFIG, + MOBILE_CONFIG, + COMPATIBILITY +} from '../environment' + +const exportedToAllEnvs = [CLIENT, COMPATIBILITY, SERVER, UNIVERSAL] +const exportedToClientEnvs = [CLIENT, COMPATIBILITY, UNIVERSAL] +const exportedToWebEnvs = [CLIENT, COMPATIBILITY, UNIVERSAL] +const exportedToServerEnvs = [SERVER, UNIVERSAL] + + +export default { + // accounts-base + Accounts: exportedToAllEnvs, + AccountsClient: exportedToClientEnvs, + AccountsServer: exportedToServerEnvs, + + // autoupdate + Autoupdate: exportedToAllEnvs, + + // babel-compiler + Babel: exportedToServerEnvs, + BabelCompiler: exportedToServerEnvs, + + // babel-runtime + babelHelpers: exportedToAllEnvs, + + // binary-heap + MaxHeap: exportedToAllEnvs, + MinHeap: exportedToAllEnvs, + MinMaxHeap: exportedToAllEnvs, + + // blaze + Blaze: exportedToAllEnvs, + UI: exportedToAllEnvs, + Handlebars: exportedToAllEnvs, + + // boilerplate-generator + Boilerplate: exportedToServerEnvs, + + // browser-policy-common + BrowserPolicy: exportedToServerEnvs, + + // caching-compiler + CachingCompiler: exportedToServerEnvs, + MultiFileCachingCompiler: exportedToServerEnvs, + + // caching-html-compiler + CachingHtmlCompiler: exportedToServerEnvs, + + // check + check: exportedToAllEnvs, + Match: exportedToAllEnvs, + + // constraint-solver + ConstraintSolver: exportedToAllEnvs, + + // ddp-client + // ddp + DDP: exportedToAllEnvs, + + // ddp-common + DDPCommon: exportedToAllEnvs, + + // ddp-rate-limiter + DDPRateLimiter: exportedToAllEnvs, + + // ddp-server + // ddp + DDPServer: exportedToAllEnvs, + + // deps + Tracker: exportedToAllEnvs, + Deps: exportedToAllEnvs, + + // diff-sequence + DiffSequence: exportedToAllEnvs, + + // ecmascript-runtime + // disabled because the babel-eslint parser defines them anyways + // Symbol: exportedToAllEnvs, + // Map: exportedToAllEnvs, + // Set: exportedToAllEnvs, + + // ecmascript + ECMAScript: exportedToAllEnvs, + + // ejson + EJSON: exportedToAllEnvs, + + // email + Email: exportedToServerEnvs, + EmailInternals: exportedToServerEnvs, + + // es5-shim + // Date: exportedToAllEnvs, + // parseInt: exportedToAllEnvs + + // facebook + Facebook: exportedToAllEnvs, + + // fastclick + FastClick: exportedToWebEnvs, + + // geojson-utils + GeoJSON: exportedToAllEnvs, + + // github + Github: exportedToAllEnvs, + + // google + Google: exportedToAllEnvs, + + // html-tools + HTMLTools: exportedToAllEnvs, + + // htmljs + HTML: exportedToAllEnvs, + + // http + HTTP: exportedToAllEnvs, + + // jquery + $: exportedToClientEnvs, + jQuery: exportedToClientEnvs, + + // launch-screen + LaunchScreen: exportedToAllEnvs, + + // logging + Log: exportedToAllEnvs, + + // logic-solver + Logic: exportedToAllEnvs, + + // markdown + Showdown: exportedToAllEnvs, + + // meetup + Meetup: exportedToAllEnvs, + + // meteor-developer + MeteorDeveloperAccounts: exportedToAllEnvs, + + // meteor + Meteor: exportedToAllEnvs, + + // minifiers + CssTools: exportedToAllEnvs, + UglifyJSMinify: exportedToAllEnvs, + UglifyJS: exportedToAllEnvs, + + // minimongo + LocalCollection: exportedToAllEnvs, + Minimongo: exportedToAllEnvs, + + // mongo-id + MongoID: exportedToAllEnvs, + + // mongo + MongoInternals: exportedToServerEnvs, + Mongo: exportedToAllEnvs, + + // npm-mongo + NpmModuleMongodb: exportedToServerEnvs, + NpmModuleMongodbVersion: exportedToServerEnvs, + + // oauth-encryption + OAuthEncryption: exportedToServerEnvs, + + // oauth + OAuth: exportedToAllEnvs, + Oauth: exportedToAllEnvs, + + // oauth1 + OAuth1Binding: exportedToServerEnvs, + + // ordered-dict + OrderedDict: exportedToAllEnvs, + + // package-version-parser + PackageVersion: exportedToAllEnvs, + + // promise + Promise: exportedToAllEnvs, + + // random + Random: exportedToAllEnvs, + + // rate-limit + RateLimiter: exportedToAllEnvs, + + // reactive-dict + ReactiveDict: exportedToAllEnvs, + + // reactive-var + ReactiveVar: exportedToAllEnvs, + + // reload + Reload: exportedToClientEnvs, + + // route-policy + RoutePolicy: exportedToServerEnvs, + + // service-configuration + ServiceConfiguration: exportedToAllEnvs, + + // session + Session: exportedToClientEnvs, + + // sha + SHA256: exportedToAllEnvs, + + // spacebars-compiler + SpacebarsCompiler: exportedToAllEnvs, + + // spacebars + Spacebars: exportedToAllEnvs, + + // spiderable + Spiderable: exportedToAllEnvs, + + // templating-tools + TemplatingTools: exportedToAllEnvs, + + // templating + Template: exportedToClientEnvs, + + // tinytest + Tinytest: exportedToAllEnvs, + + // tracker + // Tracker: exportedToAllEnvs, + // Deps: exportedToAllEnvs, + + // twitter + Twitter: exportedToAllEnvs, + + // ui + // Blaze: exportedToAllEnvs, + // UI: exportedToAllEnvs, + // Handlebars: exportedToAllEnvs, + + // underscore + _: exportedToAllEnvs, + + // url + URL: exportedToAllEnvs, + + // webapp-hashing + WebAppHashing: exportedToAllEnvs, + + // webapp + WebApp: exportedToAllEnvs, + main: exportedToServerEnvs, + WebAppInternals: exportedToServerEnvs, + + // weibo + Weibo: exportedToAllEnvs, + + // xmlbuilder + XmlBuilder: exportedToServerEnvs, + + + // globals from npm package "gloabls" + // used by setting "env: meteor" in .eslintrc) + App: [MOBILE_CONFIG], + Assets: [SERVER, UNIVERSAL], + Cordova: [PACKAGE_CONFIG], + Npm: [PACKAGE_CONFIG, SERVER, UNIVERSAL], + Package: [...exportedToAllEnvs, PACKAGE_CONFIG], + Plugin: [PACKAGE], + process: [SERVER, UNIVERSAL] + // Router: false, + // share: false, + // Utils: false +} diff --git a/package.json b/package.json index 4373018356..766f3b3236 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "homepage": "https://github.com/dferber90/eslint-plugin-meteor", "bugs": "https://github.com/dferber90/eslint-plugin-meteor/issues", "dependencies": { + "escope": "3.2.0", "invariant": "2.1.1", "lodash.find": "3.2.1", "path-exists": "2.0.0" diff --git a/tests/lib/rules/globals.js b/tests/lib/rules/globals.js new file mode 100644 index 0000000000..dd18cc9d49 --- /dev/null +++ b/tests/lib/rules/globals.js @@ -0,0 +1,32 @@ +/** + * @fileoverview Definitions for global Meteor variables based on environment + * @author Dominik Ferber + * @copyright 2015 Dominik Ferber. All rights reserved. + * See LICENSE file in root directory for full license. + */ + +// ----------------------------------------------------------------------------- +// Requirements +// ----------------------------------------------------------------------------- + +import {SERVER} from '../../../dist/util/environment' +const rule = require('../../../dist/rules/globals') +const RuleTester = require('eslint').RuleTester + + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +const ruleTester = new RuleTester() +ruleTester.run('globals', rule(() => ({env: SERVER, isLintedEnv: true})), { + + valid: ['Session.set("hi", true)'], + invalid: [] + +}) + +ruleTester.run('globals', rule(() => ({env: SERVER, isLintedEnv: false})), { + valid: ['Session.set("hi", true)'], + invalid: [] +})