diff --git a/README.md b/README.md index d625a49..cd1f748 100755 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ For a more thorough introduction, read [setting up a Meteor project](docs/SETUP_ * [check](docs/rules/check.md): Core API for check and Match * [connections](docs/rules/connections.md): Core API for connections * [collections](docs/rules/collections.md): Core API for collections +* [session](docs/rules/session.md): Core API for Session ## Best Practices * [audit-argument-checks](docs/rules/audit-argument-checks.md): Enforce check on all arguments passed to methods and publish functions diff --git a/docs/rules/session.md b/docs/rules/session.md new file mode 100644 index 0000000..8fb5eb9 --- /dev/null +++ b/docs/rules/session.md @@ -0,0 +1,114 @@ +# Core API for Session (session) + +Prevent misusage of [Session](http://docs.meteor.com/#/full/session). + + +## Rule Details + +This rule aims to prevent errors when using Publications and Subscriptions. It verifies `Session` is used in the correct environments. + +The following patterns are considered warnings: + +```js + +Session.set('foo') + +``` + +```js + +Session.setDefault('foo') + +``` + + +```js + +Session.set('foo', true, 'bar') + +``` + + +```js + +if (Meteor.isServer) { + Session.set('foo') +} + +``` + + +```js + +Session.get('foo', true) + +``` + + +```js + +Session.get() + +``` + + +```js + +Session.equals('foo') + +``` + +The following patterns are not warnings: + +```js + +Session.set('foo', true) + +``` + +```js + +Session.setDefault('foo', true) + +``` + +```js + +Session.get('foo') + +``` + +```js + +Session.equals('foo', true) + +``` + +### Options + +#### no-equal + +By default this rule does not warn when trying to call `Session.equal`. Usually a call to `Session.equals` is meant instead. +To warn when using `Session.equal`, configure the rule as + +``` + +session: [2, "no-equal"] + +``` + +With this configuration, the rule will warn on this pattern: + +```js + +Session.equal('foo', 'bar') + +``` + +## When Not To Use It + +Disable this rule if you are using [no-session](./no-session.md). + +## Further Reading + +- http://docs.meteor.com/#/full/session diff --git a/lib/index.js b/lib/index.js index 0d0cfb3..f097ca1 100755 --- a/lib/index.js +++ b/lib/index.js @@ -22,6 +22,7 @@ module.exports = { check: unpack('./rules/check'), connections: unpack('./rules/connections'), collections: unpack('./rules/collections'), + session: unpack('./rules/session'), // Best Practices 'audit-argument-checks': unpack('./rules/audit-argument-checks'), @@ -39,6 +40,7 @@ module.exports = { check: 0, connections: 0, collections: 0, + session: 0, // Best Practices 'audit-argument-checks': 0, diff --git a/lib/rules/session.js b/lib/rules/session.js new file mode 100644 index 0000000..d8d45b4 --- /dev/null +++ b/lib/rules/session.js @@ -0,0 +1,73 @@ +/** + * @fileoverview Core API for Session + * @author Dominik Ferber + * @copyright 2015 Dominik Ferber. All rights reserved. + * See LICENSE file in root directory for full license. + */ + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +import {NON_METEOR, SERVER} from '../util/environment' +import {getExecutors} from '../util' +import {getPropertyName} from '../util/ast' + +module.exports = getMeta => context => { + const {env} = getMeta(context) + + // --------------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------------- + + if (env === NON_METEOR || env === SERVER) { + return {} + } + + return { + + CallExpression: function (node) { + if ( + node.callee.type === 'MemberExpression' && + node.callee.object.type === 'Identifier' && + node.callee.object.name === 'Session' + ) { + const executors = getExecutors(env, context.getAncestors()) + if (executors.size === 0) { + return + } + if (executors.has('server')) { + context.report(node, 'Allowed on client only') + return + } + + switch (getPropertyName(node.callee.property)) { + case 'set': + case 'setDefault': + case 'equals': + if (node.arguments.length !== 2) { + context.report(node, 'Expected two arguments') + } + break + case 'get': + if (node.arguments.length !== 1) { + context.report(node, 'Expected one argument') + } + break + case 'equal': + if (context.options.length > 0 && context.options[0] === 'no-equal') { + context.report(node.callee.property, 'Did you mean "Session.equals" instead?') + } + break + } + } + } + + } +} + +module.exports.schema = [ + { + enum: ['equal', 'no-equal'] + } +] diff --git a/tests/lib/rules/session.js b/tests/lib/rules/session.js new file mode 100644 index 0000000..9d54af0 --- /dev/null +++ b/tests/lib/rules/session.js @@ -0,0 +1,121 @@ +/** + * @fileoverview Core API for Session + * @author Dominik Ferber + * @copyright 2015 Dominik Ferber. All rights reserved. + * See LICENSE file in root directory for full license. + */ + +// ----------------------------------------------------------------------------- +// Requirements +// ----------------------------------------------------------------------------- + +import {NON_METEOR, UNIVERSAL, CLIENT, SERVER} from '../../../dist/util/environment' +const rule = require('../../../dist/rules/session') +const RuleTester = require('eslint').RuleTester + +const commonValidCode = [ + 'x()', + 'Session.set("foo", true)', + 'Session.setDefault("foo", true)', + 'Session.get("foo")', + 'Session.equals("foo", true)', + { + code: `Session.equal('foo', 'bar')`, + options: ['equal'] + }, + ` + if (Meteor.isServer) { + Session.set('foo') + } + ` +] + +const commonInvalidCode = [ + { + code: `Session.set('foo')`, + errors: [{message: 'Expected two arguments', type: 'CallExpression'}] + }, + { + code: `Session.setDefault('foo')`, + errors: [{message: 'Expected two arguments', type: 'CallExpression'}] + }, + { + code: `Session.set('foo', true, 'bar')`, + errors: [{message: 'Expected two arguments', type: 'CallExpression'}] + }, + { + code: ` + Session.get('foo', true) + `, + errors: [{message: 'Expected one argument', type: 'CallExpression'}] + }, + { + code: `Session.get()`, + errors: [{message: 'Expected one argument', type: 'CallExpression'}] + }, + { + code: `Session.equals('foo')`, + errors: [{message: 'Expected two arguments', type: 'CallExpression'}] + }, + { + code: `Session.equal('foo', 'bar')`, + options: ['no-equal'], + errors: [{message: 'Did you mean "Session.equals" instead?', type: 'Identifier'}] + } +] + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +const ruleTester = new RuleTester() + +ruleTester.run('session', rule(() => ({env: CLIENT})), { + valid: [ + ...commonValidCode + ], + + invalid: [ + ...commonInvalidCode + ] +}) + +ruleTester.run('session', rule(() => ({env: UNIVERSAL})), { + valid: [ + ` + if (Meteor.isClient) { + Session.set('foo', true) + } + `, + ` + if (Meteor.isCordova) { + Session.set('foo', true) + } + ` + ], + + invalid: [ + { + code: `Session.set('foo', true)`, + errors: [{message: 'Allowed on client only', type: 'CallExpression'}] + } + ] +}) + +ruleTester.run('session', rule(() => ({env: SERVER})), { + valid: [ + ...commonValidCode, + ...commonInvalidCode + ], + + invalid: [] +}) + +ruleTester.run('session', rule(() => ({env: NON_METEOR})), { + valid: [ + ...commonValidCode, + ...commonInvalidCode + ], + + invalid: [] +})