Skip to content
This repository has been archived by the owner on Jan 19, 2019. It is now read-only.

Fix: camelcase false positives on interface properties (fixes #177) #183

Merged
merged 5 commits into from Nov 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -47,6 +47,7 @@ This guarantees 100% compatibility between the plugin and the parser.
<!-- Please run `npm run docs` to update this section -->
<!-- begin rule list -->
* [`typescript/adjacent-overload-signatures`](./docs/rules/adjacent-overload-signatures.md) — Require that member overloads be consecutive
* [`typescript/camelcase`](./docs/rules/camelcase.md) — Enforce camelCase naming convention
* [`typescript/class-name-casing`](./docs/rules/class-name-casing.md) — Require PascalCased class and interface names (`class-name` from TSLint)
* [`typescript/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) — Require explicit return types on functions and class methods
* [`typescript/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) — Require explicit accessibility modifiers on class properties and methods (`member-access` from TSLint)
Expand Down
195 changes: 195 additions & 0 deletions docs/rules/camelcase.md
@@ -0,0 +1,195 @@
# Enforce camelCase naming convention (camelcase)

When it comes to naming variables, style guides generally fall into one of two
camps: camelcase (`variableName`) and underscores (`variable_name`). This rule
focuses on using the camelcase approach. If your style guide calls for
camelCasing your variable names, then this rule is for you!

## Rule Details

This rule looks for any underscores (`_`) located within the source code.
It ignores leading and trailing underscores and only checks those in the middle
of a variable name. If ESLint decides that the variable is a constant
(all uppercase), then no warning will be thrown. Otherwise, a warning will be
thrown. This rule only flags definitions and assignments but not function calls.
In case of ES6 `import` statements, this rule only targets the name of the
variable that will be imported into the local module scope.

***This rule was taken from the ESLint core rule `camelcase`.***
***Available options and test cases may vary depending on the version of ESLint installed in the system.***

## Options

This rule has an object option:

* `"properties": "always"` (default) enforces camelcase style for property names
* `"properties": "never"` does not check property names
* `"ignoreDestructuring": false` (default) enforces camelcase style for destructured identifiers
* `"ignoreDestructuring": true` does not check destructured identifiers
* `allow` (`string[]`) list of properties to accept. Accept regex.

### properties: "always"

Examples of **incorrect** code for this rule with the default `{ "properties": "always" }` option:

```js
/*eslint camelcase: "error"*/

import { no_camelcased } from "external-module"

var my_favorite_color = "#112C85";

function do_something() {
// ...
}

obj.do_something = function() {
// ...
};

function foo({ no_camelcased }) {
// ...
};

function foo({ isCamelcased: no_camelcased }) {
// ...
}

function foo({ no_camelcased = 'default value' }) {
// ...
};

var obj = {
my_pref: 1
};

var { category_id = 1 } = query;

var { foo: no_camelcased } = bar;

var { foo: bar_baz = 1 } = quz;
```

Examples of **correct** code for this rule with the default `{ "properties": "always" }` option:

```js
/*eslint camelcase: "error"*/

import { no_camelcased as camelCased } from "external-module";

var myFavoriteColor = "#112C85";
var _myFavoriteColor = "#112C85";
var myFavoriteColor_ = "#112C85";
var MY_FAVORITE_COLOR = "#112C85";
var foo = bar.baz_boom;
var foo = { qux: bar.baz_boom };

obj.do_something();
do_something();
new do_something();

var { category_id: category } = query;

function foo({ isCamelCased }) {
// ...
};

function foo({ isCamelCased: isAlsoCamelCased }) {
// ...
}

function foo({ isCamelCased = 'default value' }) {
// ...
};

var { categoryId = 1 } = query;

var { foo: isCamelCased } = bar;

var { foo: isCamelCased = 1 } = quz;

```

### properties: "never"

Examples of **correct** code for this rule with the `{ "properties": "never" }` option:

```js
/*eslint camelcase: ["error", {properties: "never"}]*/

var obj = {
my_pref: 1
};
```

### ignoreDestructuring: false

Examples of **incorrect** code for this rule with the default `{ "ignoreDestructuring": false }` option:

```js
/*eslint camelcase: "error"*/

var { category_id } = query;

var { category_id = 1 } = query;

var { category_id: category_id } = query;

var { category_id: category_alias } = query;

var { category_id: categoryId, ...other_props } = query;
```

### ignoreDestructuring: true

Examples of **incorrect** code for this rule with the `{ "ignoreDestructuring": true }` option:

```js
/*eslint camelcase: ["error", {ignoreDestructuring: true}]*/

var { category_id: category_alias } = query;

var { category_id, ...other_props } = query;
```

Examples of **correct** code for this rule with the `{ "ignoreDestructuring": true }` option:

```js
/*eslint camelcase: ["error", {ignoreDestructuring: true}]*/

var { category_id } = query;

var { category_id = 1 } = query;

var { category_id: category_id } = query;
```

## allow

Examples of **correct** code for this rule with the `allow` option:

```js
/*eslint camelcase: ["error", {allow: ["UNSAFE_componentWillMount"]}]*/

function UNSAFE_componentWillMount() {
// ...
}
```

```js
/*eslint camelcase: ["error", {allow: ["^UNSAFE_"]}]*/

function UNSAFE_componentWillMount() {
// ...
}

function UNSAFE_componentWillMount() {
// ...
}
```

## When Not To Use It

If you have established coding standards using a different naming convention (separating words with underscores), turn this rule off.

<sup>Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/camelcase.md)</sup>
114 changes: 114 additions & 0 deletions lib/rules/camelcase.js
@@ -0,0 +1,114 @@
/**
* @fileoverview Rule to flag non-camelcased identifiers
* @author Patricio Trevino
*/
"use strict";

const baseRule = require("eslint/lib/rules/camelcase");

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

module.exports = {
meta: Object.assign({}, baseRule.meta, {
docs: {
description: "Enforce camelCase naming convention",
},
}),

create(context) {
const rules = baseRule.create(context);
const TS_PROPERTY_TYPES = [
"TSPropertySignature",
"ClassProperty",
"TSParameterProperty",
"TSAbstractClassProperty",
];

const options = context.options[0] || {};
let properties = options.properties || "";
const allow = options.allow || [];

if (properties !== "always" && properties !== "never") {
properties = "always";
}

/**
* Checks if a string contains an underscore and isn't all upper-case
* @param {string} name The string to check.
* @returns {boolean} if the string is underscored
* @private
*/
function isUnderscored(name) {
// if there's an underscore, it might be A_CONSTANT, which is okay
return name.indexOf("_") > -1 && name !== name.toUpperCase();
}

/**
* Checks if a string match the ignore list
* @param {string} name The string to check.
* @returns {boolean} if the string is ignored
* @private
*/
function isAllowed(name) {
return (
allow.findIndex(
entry => name === entry || name.match(new RegExp(entry))
) !== -1
);
}

/**
* Checks if the the node is a valid TypeScript property type.
* @param {Node} node the node to be validated.
* @returns {boolean} true if the node is a TypeScript property type.
* @private
*/
function isTSPropertyType(node) {
if (!node.parent) return false;
if (TS_PROPERTY_TYPES.includes(node.parent.type)) return true;

if (node.parent.type === "AssignmentPattern") {
return (
node.parent.parent &&
TS_PROPERTY_TYPES.includes(node.parent.parent.type)
);
}

return false;
}

return {
Identifier(node) {
/*
* Leading and trailing underscores are commonly used to flag
* private/protected identifiers, strip them
*/
const name = node.name.replace(/^_+|_+$/g, "");

// First, we ignore the node if it match the ignore list
if (isAllowed(name)) {
return;
}

// Check TypeScript specific nodes
if (isTSPropertyType(node)) {
if (properties === "always" && isUnderscored(name)) {
context.report({
node,
messageId: "notCamelCase",
data: { name: node.name },
});
}

return;
}

// Let the base rule deal with the rest
// eslint-disable-next-line new-cap
rules.Identifier(node);
},
};
},
};