From 12036a2d71598892e9e06cb68fe564e4b82184a1 Mon Sep 17 00:00:00 2001 From: Dominik Ferber Date: Tue, 8 Mar 2016 13:44:34 +0100 Subject: [PATCH] feat(rule): add rule template-naming-convention closes #22 --- README.md | 1 + docs/rules/template-naming-convention.md | 76 +++++++++++++ lib/index.js | 2 + lib/rules/template-naming-convention.js | 68 ++++++++++++ tests/lib/rules/template-naming-convention.js | 104 ++++++++++++++++++ 5 files changed, 251 insertions(+) create mode 100644 docs/rules/template-naming-convention.md create mode 100644 lib/rules/template-naming-convention.js create mode 100644 tests/lib/rules/template-naming-convention.js diff --git a/README.md b/README.md index f30c0d4..223c44f 100755 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ For a more thorough introduction, read the [setup guide](/docs/guides/setup.md). * [no-zero-timeout](docs/rules/no-zero-timeout.md): Prevent usage of Meteor.setTimeout with zero delay * [blaze-consistent-eventmap-params](docs/rules/blaze-consistent-eventmap-params.md): Force consistent event handler parameters in event maps * [prefer-session-equals](docs/prefer-session-equals.md): Prefer `Session.equals` in conditions +* [template-naming-convention](docs/template-naming-convention.md): Naming convention for templates ## Core API diff --git a/docs/rules/template-naming-convention.md b/docs/rules/template-naming-convention.md new file mode 100644 index 0000000..b4aaef1 --- /dev/null +++ b/docs/rules/template-naming-convention.md @@ -0,0 +1,76 @@ +# Force a naming convention for templates (template-naming-convention) + +When it comes to naming templates there are multiple naming conventions available. Enforce one of them with this rule. + + +## Rule Details + +This rule aims to enforce one naming convention for template names is used consistently. +It does this by checking references to the template from the JavaScript code. + +It offers three different naming conventions, one of which can be chosen through the rule options. + +The following patterns are considered warnings: + +```js + +/*eslint meteor/template-naming-convention: [2, "camel-case"]*/ +Template.foo_bar.onCreated +Template.foo_bar.onRendered +Template.foo_bar.onDestroyed +Template.foo_bar.events +Template.foo_bar.helpers + +Template.foo_bar.onCreated() +/* .. */ + +Template.FooBar.onCreated +/* .. */ + +``` + +The following patterns are not warnings: + +```js + +/*eslint meteor/template-naming-convention: [2, "camel-case"]*/ +Template.fooBar.onCreated +Template.fooBar.onRendered +Template.fooBar.onDestroyed +Template.fooBar.events +Template.fooBar.helpers + +/*eslint meteor/template-naming-convention: [2, "pascal-case"]*/ +Template.FooBar.onCreated +/* .. */ + +/*eslint meteor/template-naming-convention: [2, "snake-case"]*/ +Template.foo.onCreated +Template.foo_bar.onCreated + +``` + +### Options + +This rule accepts a single options argument with the following defaults: + +```json +{ + "rules": { + "template-naming-convention": [2, "camel-case"] + } +} +``` + +The second argument can have the following values: +- `camel-case` +- `pascal-case` +- `snake-case` + +## Limitations + +This rule can not warn for templates which are never referenced in JavaScript. + +## When Not To Use It + +If you are not using Blaze templates, it is okay to turn this rule off. diff --git a/lib/index.js b/lib/index.js index 0697227..4b7999b 100755 --- a/lib/index.js +++ b/lib/index.js @@ -6,6 +6,7 @@ module.exports = { 'no-zero-timeout': require('./rules/no-zero-timeout'), 'blaze-consistent-eventmap-params': require('./rules/blaze-consistent-eventmap-params'), 'prefer-session-equals': require('./rules/prefer-session-equals'), + 'template-naming-convention': require('./rules/template-naming-convention'), }, configs: { parserOptions: { @@ -21,6 +22,7 @@ module.exports = { 'meteor/no-zero-timeout': 2, 'meteor/blaze-consistent-eventmap-params': 2, 'meteor/prefer-session-equals': 0, + 'meteor/template-naming-convention': 2, }, }, }, diff --git a/lib/rules/template-naming-convention.js b/lib/rules/template-naming-convention.js new file mode 100644 index 0000000..88f4faf --- /dev/null +++ b/lib/rules/template-naming-convention.js @@ -0,0 +1,68 @@ +/** + * @fileoverview Force a naming convention for templates + * @author Dominik Ferber + * @copyright 2016 Dominik Ferber. All rights reserved. + * See LICENSE file in root directory for full license. + */ + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- +const templateProps = new Set([ + 'onCreated', + 'onRendered', + 'onDestroyed', + 'events', + 'helpers', + 'created', + 'rendered', + 'destroyed', +]) + +const NAMING_CONVENTIONS = { + CAMEL: 'camel-case', + PASCAL: 'pascal-case', + SNAKE: 'snake-case', +} + +const isTemplateMemberExpression = node => ( + node.object.type === 'MemberExpression' && + node.object.object.type === 'Identifier' && + node.object.object.name === 'Template' && + node.object.property.type === 'Identifier' && + node.property.type === 'Identifier' && + templateProps.has(node.property.name) +) + +const getErrorMessage = expected => `Invalid template naming convention, expected "${expected}"` + +module.exports = context => ({ + MemberExpression: node => { + if (!isTemplateMemberExpression(node)) return + + const [namingConvention] = context.options + const templateName = node.object.property.name + switch (namingConvention) { + case NAMING_CONVENTIONS.PASCAL: + if (!/^[A-Z]([A-Z]|[a-z]|[0-9])*$/.test(templateName)) { + context.report(node, getErrorMessage(NAMING_CONVENTIONS.PASCAL)) + } + break + case NAMING_CONVENTIONS.SNAKE: + if (templateName.toLowerCase() !== templateName) { + context.report(node, getErrorMessage(NAMING_CONVENTIONS.SNAKE)) + } + break + case NAMING_CONVENTIONS.CAMEL: + default: + if (!/^[a-z]([A-Z]|[a-z]|[0-9])+$/.test(templateName)) { + context.report(node, getErrorMessage(NAMING_CONVENTIONS.CAMEL)) + } + break + } + }, +}) + +module.exports.schema = [ + { enum: Object.values(NAMING_CONVENTIONS) }, +] diff --git a/tests/lib/rules/template-naming-convention.js b/tests/lib/rules/template-naming-convention.js new file mode 100644 index 0000000..9c37e0a --- /dev/null +++ b/tests/lib/rules/template-naming-convention.js @@ -0,0 +1,104 @@ +/** + * @fileoverview Force a naming convention for templates + * @author Dominik Ferber + * @copyright 2016 Dominik Ferber. All rights reserved. + * See LICENSE file in root directory for full license. + */ + +// ----------------------------------------------------------------------------- +// Requirements +// ----------------------------------------------------------------------------- + +const rule = require('../../../dist/rules/template-naming-convention') +const RuleTester = require('eslint').RuleTester +const ruleTester = new RuleTester() + +ruleTester.run('template-naming-convention', rule, { + valid: [ + 'Template.foo.helpers', + 'Template.foo01.helpers', + 'Template.foo19bar.helpers', + 'Template.fooBar.helpers', + 'Template.fooBar.helpers({})', + { + code: 'Template.FooBar.helpers({})', + options: ['pascal-case'], + }, + { + code: 'Template.foo_bar.helpers({})', + options: ['snake-case'], + }, + { + code: 'Template.fooBar.helpers({})', + options: ['camel-case'], + }, + { + code: 'Template.fooBar.helpers({})', + options: [], + }, + ], + + invalid: [ + { + code: 'Template.foo_bar.onCreated', + errors: [ + { message: 'Invalid template naming convention, expected "camel-case"', type: 'MemberExpression' }, + ], + }, + { + code: 'Template.foo_bar.onRendered', + errors: [ + { message: 'Invalid template naming convention, expected "camel-case"', type: 'MemberExpression' }, + ], + }, + { + code: 'Template.foo_bar.onDestroyed', + errors: [ + { message: 'Invalid template naming convention, expected "camel-case"', type: 'MemberExpression' }, + ], + }, + { + code: 'Template.foo_bar.events', + errors: [ + { message: 'Invalid template naming convention, expected "camel-case"', type: 'MemberExpression' }, + ], + }, + { + code: 'Template.foo_bar.helpers', + errors: [ + { message: 'Invalid template naming convention, expected "camel-case"', type: 'MemberExpression' }, + ], + }, + { + code: 'Template.foo_bar.created', + errors: [ + { message: 'Invalid template naming convention, expected "camel-case"', type: 'MemberExpression' }, + ], + }, + { + code: 'Template.foo_bar.rendered', + errors: [ + { message: 'Invalid template naming convention, expected "camel-case"', type: 'MemberExpression' }, + ], + }, + { + code: 'Template.foo_bar.destroyed', + errors: [ + { message: 'Invalid template naming convention, expected "camel-case"', type: 'MemberExpression' }, + ], + }, + { + code: 'Template.foo_bar.helpers({})', + errors: [ + { message: 'Invalid template naming convention, expected "camel-case"', type: 'MemberExpression' }, + ], + }, + { + code: 'Template.fooBar.helpers({})', + options: ['snake-case'], + errors: [ + { message: 'Invalid template naming convention, expected "snake-case"', type: 'MemberExpression' }, + ], + }, + ], +})