Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Chore: add internal rule that validates meta property (fixes #6383) (#…
  • Loading branch information
vitorbal authored and nzakas committed Jul 13, 2016
1 parent 4adb15f commit 5985eb2
Show file tree
Hide file tree
Showing 8 changed files with 489 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -12,3 +12,5 @@ versions.json
.eslintcache
.cache
/packages/**/node_modules
.vscode/setting.json
.sublimelinterrc
2 changes: 1 addition & 1 deletion Makefile.js
Expand Up @@ -57,7 +57,7 @@ var NODE = "node ", // intentional extra space

// Utilities - intentional extra space at the end of each string
MOCHA = NODE_MODULES + "mocha/bin/_mocha ",
ESLINT = NODE + " bin/eslint.js ",
ESLINT = NODE + " bin/eslint.js --rulesdir lib/internal-rules/ ",

// Files
MAKEFILE = "./Makefile.js",
Expand Down
212 changes: 212 additions & 0 deletions lib/internal-rules/internal-no-invalid-meta.js
@@ -0,0 +1,212 @@
/**
* @fileoverview Internal rule to prevent missing or invalid meta property in core rules.
* @author Vitor Balocco
*/

"use strict";

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
* Gets the property of the Object node passed in that has the name specified.
*
* @param {string} property Name of the property to return.
* @param {ASTNode} node The ObjectExpression node.
* @returns {ASTNode} The Property node or null if not found.
*/
function getPropertyFromObject(property, node) {
var properties = node.properties;

for (var i = 0; i < properties.length; i++) {
if (properties[i].key.name === property) {
return properties[i];
}
}

return null;
}

/**
* Extracts the `meta` property from the ObjectExpression that all rules export.
*
* @param {ASTNode} exportsNode ObjectExpression node that the rule exports.
* @returns {ASTNode} The `meta` Property node or null if not found.
*/
function getMetaPropertyFromExportsNode(exportsNode) {
return getPropertyFromObject("meta", exportsNode);
}

/**
* Whether this `meta` ObjectExpression has a `docs` property defined or not.
*
* @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
* @returns {boolean} `true` if a `docs` property exists.
*/
function hasMetaDocs(metaPropertyNode) {
return Boolean(getPropertyFromObject("docs", metaPropertyNode.value));
}

/**
* Whether this `meta` ObjectExpression has a `docs.description` property defined or not.
*
* @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
* @returns {boolean} `true` if a `docs.description` property exists.
*/
function hasMetaDocsDescription(metaPropertyNode) {
var metaDocs = getPropertyFromObject("docs", metaPropertyNode.value);

return metaDocs && getPropertyFromObject("description", metaDocs.value);
}

/**
* Whether this `meta` ObjectExpression has a `docs.category` property defined or not.
*
* @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
* @returns {boolean} `true` if a `docs.category` property exists.
*/
function hasMetaDocsCategory(metaPropertyNode) {
var metaDocs = getPropertyFromObject("docs", metaPropertyNode.value);

return metaDocs && getPropertyFromObject("category", metaDocs.value);
}

/**
* Whether this `meta` ObjectExpression has a `docs.recommended` property defined or not.
*
* @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
* @returns {boolean} `true` if a `docs.recommended` property exists.
*/
function hasMetaDocsRecommended(metaPropertyNode) {
var metaDocs = getPropertyFromObject("docs", metaPropertyNode.value);

return metaDocs && getPropertyFromObject("recommended", metaDocs.value);
}

/**
* Whether this `meta` ObjectExpression has a `schema` property defined or not.
*
* @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
* @returns {boolean} `true` if a `schema` property exists.
*/
function hasMetaSchema(metaPropertyNode) {
return getPropertyFromObject("schema", metaPropertyNode.value);
}

/**
* Whether this `meta` ObjectExpression has a `fixable` property defined or not.
*
* @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
* @returns {boolean} `true` if a `fixable` property exists.
*/
function hasMetaFixable(metaPropertyNode) {
return getPropertyFromObject("fixable", metaPropertyNode.value);
}

/**
* Checks the validity of the meta definition of this rule and reports any errors found.
*
* @param {RuleContext} context The ESLint rule context.
* @param {ASTNode} exportsNode ObjectExpression node that the rule exports.
* @param {boolean} ruleIsFixable whether the rule is fixable or not.
* @returns {void}
*/
function checkMetaValidity(context, exportsNode, ruleIsFixable) {
var metaProperty = getMetaPropertyFromExportsNode(exportsNode);

if (!metaProperty) {
context.report(exportsNode, "Rule is missing a meta property.");
return;
}

if (!hasMetaDocs(metaProperty)) {
context.report(metaProperty, "Rule is missing a meta.docs property.");
return;
}

if (!hasMetaDocsDescription(metaProperty)) {
context.report(metaProperty, "Rule is missing a meta.docs.description property.");
return;
}

if (!hasMetaDocsCategory(metaProperty)) {
context.report(metaProperty, "Rule is missing a meta.docs.category property.");
return;
}

if (!hasMetaDocsRecommended(metaProperty)) {
context.report(metaProperty, "Rule is missing a meta.docs.recommended property.");
return;
}

if (!hasMetaSchema(metaProperty)) {
context.report(metaProperty, "Rule is missing a meta.schema property.");
return;
}

if (ruleIsFixable && !hasMetaFixable(metaProperty)) {
context.report(metaProperty, "Rule is fixable, but is missing a meta.fixable property.");
return;
}
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: "enforce correct use of `meta` property in core rules",
category: "Internal",
recommended: false
},

schema: []
},

create: function(context) {
var metaExportsValue;
var ruleIsFixable = false;

return {
AssignmentExpression: function(node) {
if (node.left &&
node.right &&
node.left.type === "MemberExpression" &&
node.left.object.name === "module" &&
node.left.property.name === "exports") {

metaExportsValue = node.right;
}
},

CallExpression: function(node) {

// If the rule has a call for `context.report` and a property `fix`
// is being passed in, then we consider that the rule is fixable.
//
// Note that we only look for context.report() calls in the new
// style (with single MessageDescriptor argument), because only
// calls in the new style can specify a fix.
if (node.callee.type === "MemberExpression" &&
node.callee.object.type === "Identifier" &&
node.callee.object.name === "context" &&
node.callee.property.type === "Identifier" &&
node.callee.property.name === "report" &&
node.arguments.length === 1 &&
node.arguments[0].type === "ObjectExpression") {

if (getPropertyFromObject("fix", node.arguments[0])) {
ruleIsFixable = true;
}
}
},

"Program:exit": function() {
checkMetaValidity(context, metaExportsValue, ruleIsFixable);
}
};
}
};
2 changes: 2 additions & 0 deletions lib/rules/.eslintrc.yml
@@ -0,0 +1,2 @@
rules:
internal-no-invalid-meta: "error"
4 changes: 3 additions & 1 deletion lib/rules/no-prototype-builtins.js
Expand Up @@ -14,7 +14,9 @@ module.exports = {
description: "disallow calling some `Object.prototype` methods directly on objects",
category: "Possible Errors",
recommended: false
}
},

schema: []
},

create: function(context) {
Expand Down
4 changes: 3 additions & 1 deletion lib/rules/no-unsafe-finally.js
Expand Up @@ -24,7 +24,9 @@ module.exports = {
description: "disallow control flow statements in `finally` blocks",
category: "Possible Errors",
recommended: true
}
},

schema: []
},
create: function(context) {

Expand Down
4 changes: 3 additions & 1 deletion lib/rules/no-useless-computed-key.js
Expand Up @@ -16,7 +16,9 @@ module.exports = {
description: "disallow unnecessary computed property keys in object literals",
category: "ECMAScript 6",
recommended: false
}
},

schema: []
},
create: function(context) {
var sourceCode = context.getSourceCode();
Expand Down

0 comments on commit 5985eb2

Please sign in to comment.