1 | import * as ts from 'typescript';
|
2 | import * as Lint from 'tslint';
|
3 |
|
4 | import { ErrorTolerantWalker } from './utils/ErrorTolerantWalker';
|
5 | import { ExtendedMetadata } from './utils/ExtendedMetadata';
|
6 |
|
7 | export const FAILURE_MIN_STRING: string = 'Too short; difficult to understand its purpose without context';
|
8 | export const FAILURE_MAX_STRING: string = 'Too long; difficult to read and potentially less maintainable';
|
9 |
|
10 | const OPTION_MIN_STRING: string = 'min';
|
11 | const OPTION_MAX_STRING: string = 'max';
|
12 | const OPTION_EXCEPTIONS_STRING: string = 'exceptions';
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | export class Rule extends Lint.Rules.AbstractRule {
|
18 | public static metadata: ExtendedMetadata = {
|
19 | ruleName: 'id-length',
|
20 | type: 'maintainability',
|
21 | description: 'This rule enforces a minimum and/or maximum identifier length convention.',
|
22 | options: {
|
23 | definitions: {
|
24 | 'minimum-length': {
|
25 | type: 'integer',
|
26 | minimum: 1,
|
27 | default: 2,
|
28 | },
|
29 | 'maximum-length': {
|
30 | type: 'integer',
|
31 | minimum: 1,
|
32 | },
|
33 | exceptions: {
|
34 | type: 'array',
|
35 | items: {
|
36 | type: 'string',
|
37 | },
|
38 | minLength: 0,
|
39 | uniqueItems: true,
|
40 | },
|
41 | },
|
42 | type: 'array',
|
43 | items: {
|
44 | type: 'array',
|
45 | items: {
|
46 | oneOf: [
|
47 | {
|
48 | title: 'Only the minimum length',
|
49 | $ref: '#/definitions/minimum-length',
|
50 | },
|
51 | {
|
52 | title: 'Only the exceptions array',
|
53 | $ref: '#/definitions/exceptions',
|
54 | },
|
55 | {
|
56 | title: 'Configuration object',
|
57 | type: 'object',
|
58 | properties: {
|
59 | min: { $ref: '#/definitions/minimum-length' },
|
60 | max: { $ref: '#/definitions/maximum-length' },
|
61 | exceptions: { $ref: '#/definitions/exceptions' },
|
62 | },
|
63 | additionalProperties: false,
|
64 | minProperties: 1,
|
65 | },
|
66 | ],
|
67 | },
|
68 | minItems: 1,
|
69 | maxItems: 1,
|
70 | },
|
71 | },
|
72 | optionsDescription: `
|
73 | One of the following combinations can be provided:
|
74 | * Minimum desired length.
|
75 | * An array of exceptions.
|
76 | * Minimum desired length and an exceptions array.
|
77 | * A configuration object containing at least one of the following properties:
|
78 | * \`"${OPTION_MIN_STRING}"\`
|
79 | * \`"${OPTION_MAX_STRING}"\`
|
80 | * \`"${OPTION_EXCEPTIONS_STRING}"\`
|
81 | `,
|
82 | optionExamples: <any>[
|
83 | [true],
|
84 | [true, 2],
|
85 | [true, ['x', 'y', 'f', 'c']],
|
86 | [true, 2, ['x', 'y', 'f', 'c']],
|
87 | [
|
88 | true,
|
89 | {
|
90 | min: 2,
|
91 | max: 10,
|
92 | exceptions: ['x', 'y', 'f', 'c'],
|
93 | },
|
94 | ],
|
95 | ],
|
96 | typescriptOnly: true,
|
97 | issueClass: 'Non-SDL',
|
98 | issueType: 'Warning',
|
99 | severity: 'Important',
|
100 | level: 'Opportunity for Excellence',
|
101 | group: 'Correctness',
|
102 | recommendation: 'true,',
|
103 | commonWeaknessEnumeration: '',
|
104 | };
|
105 |
|
106 | public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
|
107 | return this.applyWithWalker(new IdLengthRuleWalker(sourceFile, this.getOptions()));
|
108 | }
|
109 | }
|
110 |
|
111 | class IdLengthRuleWalker extends ErrorTolerantWalker {
|
112 | private min: number = 2;
|
113 | private max: number = Infinity;
|
114 | private exceptions: string[] = [];
|
115 |
|
116 | constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) {
|
117 | super(sourceFile, options);
|
118 | this.parseOptions();
|
119 | }
|
120 |
|
121 | protected visitIdentifier(node: ts.Identifier): void {
|
122 | this.checkAndReport(node);
|
123 | super.visitIdentifier(node);
|
124 | }
|
125 |
|
126 | private checkAndReport(node: ts.Identifier): void {
|
127 | const { text } = node;
|
128 | if (this.exceptions.indexOf(text) === -1) {
|
129 | if (text.length < this.min) {
|
130 | return this.addFailureAt(node.getStart(), node.getWidth(), FAILURE_MIN_STRING + ': ' + text);
|
131 | }
|
132 | if (text.length > this.max) {
|
133 | return this.addFailureAt(node.getStart(), node.getWidth(), FAILURE_MAX_STRING + ': ' + text);
|
134 | }
|
135 | }
|
136 | }
|
137 |
|
138 | private parseOptions(): void {
|
139 | this.getOptions().forEach((opt: any) => {
|
140 | if (typeof opt === 'boolean') {
|
141 | return;
|
142 | }
|
143 | if (typeof opt === 'number') {
|
144 | this.min = opt;
|
145 | return;
|
146 | }
|
147 | if (Array.isArray(opt)) {
|
148 | this.exceptions = opt;
|
149 | return;
|
150 | }
|
151 | if (typeof opt === 'object') {
|
152 | this.min = typeof opt[OPTION_MIN_STRING] === 'number' ? opt[OPTION_MIN_STRING] : this.min;
|
153 | this.max = typeof opt[OPTION_MAX_STRING] === 'number' ? opt[OPTION_MAX_STRING] : this.max;
|
154 | this.exceptions = opt[OPTION_EXCEPTIONS_STRING] || this.exceptions;
|
155 | return;
|
156 | }
|
157 | });
|
158 | }
|
159 | }
|