Skip to content

Add no-common-typos rule #981

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions lib/rules/no-common-typos.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* @fileoverview Warn for common attribute typos.
* @author Koen Punt
*/
'use strict';

var Components = require('../util/Components');

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

var PATTERN_REGEXP = new RegExp('^/(.+)/(.+)?$');

/**
* Convert pattern string to a regular expression.
* @param {String|RegExp} string regex
* @return {RegExp}
*/
function parsePattern(pattern) {
if (PATTERN_REGEXP.exec(pattern)) {
return new RegExp(RegExp.$1, RegExp.$2);
}
return new RegExp('^' + pattern + '$');
}

module.exports = {
meta: {
docs: {
description: 'Warn for common attribute typos.',
category: 'Possible Errors',
recommended: false
},
schema: [{
type: 'array',
items: {
type: 'object',
properties: {
pattern: {
type: 'string'
},
correct: {
type: 'string'
}
},
additionalProperties: false
}
}],
fixable: 'code'
},

create: Components.detect(function(context) {

var rules = context.options[0] || [];

/**
* Checks if we are using an an incorrect attribute name.
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if an incorrect attribute name is used, false if not.
*/
function isIncorrectAttributeName(regex, correct, node) {
return Boolean(
node.type === 'JSXAttribute' &&
node.name &&
node.name.name !== correct &&
regex.test(node.name.name)
);
}

return {
JSXAttribute: function(node) {
rules.forEach(function(rule) {
var regex = parsePattern(rule.pattern);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm very concerned about using full user-provided regex patterns without sanitizing them.

Copy link
Author

@koenpunt koenpunt Nov 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why's that? Because of user's stupidity and potential rogue expressions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, exactly that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With fixing not enabled this shouldn't be an issue IMO

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

users using a shared config that then run with --fix makes it an issue.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it already so that regular expressions are always treated as string. Updated my previous comment to reflect that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm saying that the user of eslint should be protected against a malicious or accidental config, that might come from a plugin or a shared config.

Which means that the person who writes the pattern can't be trusted to mark it as safe - and neither can the user - which means the rule itself has to ensure that nothing unsafe can possibly happen.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So a predefined set of common typos, or better, common matchers like exact, case-sensitive, etc.?

Copy link
Author

@koenpunt koenpunt Nov 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example:

"no-common-typos": [{
  "case-sensitive": {
    "somepattern": "SomePattern",
    "dangerouslySetInnerHTML": "dangerouslySetInnerHTML"
  },
  "exact": {
    "colour": "color"
  }
}]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's certainly one way to do it - or alternatively, [{ pattern: "SomePattern": caseInsensitive: true }] perhaps?

if (isIncorrectAttributeName(regex, rule.correct, node)) {
var attribute = node.name.name;
context.report({
node: node,
message: 'Using possible incorrect attribute `' + attribute + '`, did you mean `' + rule.correct + '`?',
fix: function(fixer) {
return fixer.replaceText(attribute, rule.correct);
}
});
}
});
}
};
})
};
65 changes: 65 additions & 0 deletions tests/lib/rules/no-common-typos.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* @fileoverview Warn for common attribute typos.
* @author Koen Punt
*/
'use strict';

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

var rule = require('../../../lib/rules/no-common-typos');
var RuleTester = require('eslint').RuleTester;

require('babel-eslint');

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

var options = [
[
{
pattern: '/dangerouslySetInnerHTML/i',
correct: 'dangerouslySetInnerHTML'
}
]
];

var parserOptions = {
ecmaVersion: 6,
ecmaFeatures: {
jsx: true
}
};

var ruleTester = new RuleTester();
ruleTester.run('no-common-typos', rule, {

valid: [{
code: [
'var Hello = React.createClass({',
' render: function() {',
' return <div dangerouslySetInnerHTML={{__html: "Hello World"}}/>;',
' }',
'});'
].join('\n'),
options: options,
parserOptions: parserOptions
}],

invalid: [{
code: [
'var Hello = React.createClass({',
' render: function() {',
' return <div dangerouslySetInnerHtml={{__html: "Hello World"}}/>;',
' }',
'});'
].join('\n'),
options: options,
parserOptions: parserOptions,
errors: [{
message: 'Using possible incorrect attribute `dangerouslySetInnerHtml`, did you mean `dangerouslySetInnerHTML`?'
}]
}]
});