/
camelcase.ts
123 lines (110 loc) · 3.26 KB
/
camelcase.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import {
TSESTree,
AST_NODE_TYPES,
} from '@typescript-eslint/experimental-utils';
import baseRule from 'eslint/lib/rules/camelcase';
import * as util from '../util';
type Options = util.InferOptionsTypeFromRule<typeof baseRule>;
type MessageIds = util.InferMessageIdsTypeFromRule<typeof baseRule>;
export default util.createRule<Options, MessageIds>({
name: 'camelcase',
meta: {
type: 'suggestion',
docs: {
description: 'Enforce camelCase naming convention',
category: 'Stylistic Issues',
recommended: 'error',
},
schema: baseRule.meta.schema,
messages: baseRule.meta.messages,
},
defaultOptions: [
{
allow: ['^UNSAFE_'],
ignoreDestructuring: false,
properties: 'never',
},
],
create(context, [options]) {
const rules = baseRule.create(context);
const TS_PROPERTY_TYPES = [
AST_NODE_TYPES.TSPropertySignature,
AST_NODE_TYPES.ClassProperty,
AST_NODE_TYPES.TSParameterProperty,
AST_NODE_TYPES.TSAbstractClassProperty,
];
const properties = options.properties;
const allow = (options.allow || []).map(entry => ({
name: entry,
regex: new RegExp(entry),
}));
/**
* Checks if a string contains an underscore and isn't all upper-case
* @param name The string to check.
*/
function isUnderscored(name: string): boolean {
// if there's an underscore, it might be A_CONSTANT, which is okay
return name.includes('_') && name !== name.toUpperCase();
}
/**
* Checks if a string match the ignore list
* @param name The string to check.
* @returns if the string is ignored
* @private
*/
function isAllowed(name: string): boolean {
return (
allow.findIndex(
entry => name === entry.name || entry.regex.test(name),
) !== -1
);
}
/**
* Checks if the the node is a valid TypeScript property type.
* @param node the node to be validated.
* @returns true if the node is a TypeScript property type.
* @private
*/
function isTSPropertyType(node: TSESTree.Node): boolean {
if (!node.parent) {
return false;
}
if (TS_PROPERTY_TYPES.includes(node.parent.type)) {
return true;
}
if (node.parent.type === AST_NODE_TYPES.AssignmentPattern) {
return (
node.parent.parent !== undefined &&
TS_PROPERTY_TYPES.includes(node.parent.parent.type)
);
}
return false;
}
return {
Identifier(node): void {
/*
* 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
rules.Identifier(node);
},
};
},
});