UNPKG

9.37 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5}) : (function(o, m, k, k2) {
6 if (k2 === undefined) k2 = k;
7 o[k2] = m[k];
8}));
9var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10 Object.defineProperty(o, "default", { enumerable: true, value: v });
11}) : function(o, v) {
12 o["default"] = v;
13});
14var __importStar = (this && this.__importStar) || function (mod) {
15 if (mod && mod.__esModule) return mod;
16 var result = {};
17 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18 __setModuleDefault(result, mod);
19 return result;
20};
21Object.defineProperty(exports, "__esModule", { value: true });
22const utils_1 = require("@typescript-eslint/utils");
23const util = __importStar(require("../util"));
24const definition = {
25 type: 'object',
26 properties: {
27 multiline: {
28 type: 'object',
29 properties: {
30 delimiter: { enum: ['none', 'semi', 'comma'] },
31 requireLast: { type: 'boolean' },
32 },
33 additionalProperties: false,
34 },
35 singleline: {
36 type: 'object',
37 properties: {
38 // note can't have "none" for single line delimiter as it's invalid syntax
39 delimiter: { enum: ['semi', 'comma'] },
40 requireLast: { type: 'boolean' },
41 },
42 additionalProperties: false,
43 },
44 },
45 additionalProperties: false,
46};
47const isLastTokenEndOfLine = (token, line) => {
48 const positionInLine = line.indexOf(token);
49 return positionInLine === line.length - 1;
50};
51const makeFixFunction = ({ optsNone, optsSemi, lastToken, missingDelimiter, lastTokenLine, isSingleLine, }) => {
52 // if removing is the action but last token is not the end of the line
53 if (optsNone &&
54 !isLastTokenEndOfLine(lastToken.value, lastTokenLine) &&
55 !isSingleLine) {
56 return null;
57 }
58 return (fixer) => {
59 if (optsNone) {
60 // remove the unneeded token
61 return fixer.remove(lastToken);
62 }
63 const token = optsSemi ? ';' : ',';
64 if (missingDelimiter) {
65 // add the missing delimiter
66 return fixer.insertTextAfter(lastToken, token);
67 }
68 // correct the current delimiter
69 return fixer.replaceText(lastToken, token);
70 };
71};
72exports.default = util.createRule({
73 name: 'member-delimiter-style',
74 meta: {
75 type: 'suggestion',
76 docs: {
77 description: 'Require a specific member delimiter style for interfaces and type literals',
78 recommended: false,
79 },
80 fixable: 'code',
81 messages: {
82 unexpectedComma: 'Unexpected separator (,).',
83 unexpectedSemi: 'Unexpected separator (;).',
84 expectedComma: 'Expected a comma.',
85 expectedSemi: 'Expected a semicolon.',
86 },
87 schema: [
88 {
89 type: 'object',
90 properties: Object.assign({}, definition.properties, {
91 overrides: {
92 type: 'object',
93 properties: {
94 interface: definition,
95 typeLiteral: definition,
96 },
97 additionalProperties: false,
98 },
99 multilineDetection: {
100 enum: ['brackets', 'last-member'],
101 },
102 }),
103 additionalProperties: false,
104 },
105 ],
106 },
107 defaultOptions: [
108 {
109 multiline: {
110 delimiter: 'semi',
111 requireLast: true,
112 },
113 singleline: {
114 delimiter: 'semi',
115 requireLast: false,
116 },
117 multilineDetection: 'brackets',
118 },
119 ],
120 create(context, [options]) {
121 var _a;
122 const sourceCode = context.getSourceCode();
123 // use the base options as the defaults for the cases
124 const baseOptions = options;
125 const overrides = (_a = baseOptions.overrides) !== null && _a !== void 0 ? _a : {};
126 const interfaceOptions = util.deepMerge(baseOptions, overrides.interface);
127 const typeLiteralOptions = util.deepMerge(baseOptions, overrides.typeLiteral);
128 /**
129 * Check the last token in the given member.
130 * @param member the member to be evaluated.
131 * @param opts the options to be validated.
132 * @param isLast a flag indicating `member` is the last in the interface or type literal.
133 */
134 function checkLastToken(member, opts, isLast) {
135 /**
136 * Resolves the boolean value for the given setting enum value
137 * @param type the option name
138 */
139 function getOption(type) {
140 if (isLast && !opts.requireLast) {
141 // only turn the option on if its expecting no delimiter for the last member
142 return type === 'none';
143 }
144 return opts.delimiter === type;
145 }
146 let messageId = null;
147 let missingDelimiter = false;
148 const lastToken = sourceCode.getLastToken(member, {
149 includeComments: false,
150 });
151 if (!lastToken) {
152 return;
153 }
154 const sourceCodeLines = sourceCode.getLines();
155 const lastTokenLine = sourceCodeLines[(lastToken === null || lastToken === void 0 ? void 0 : lastToken.loc.start.line) - 1];
156 const optsSemi = getOption('semi');
157 const optsComma = getOption('comma');
158 const optsNone = getOption('none');
159 if (lastToken.value === ';') {
160 if (optsComma) {
161 messageId = 'expectedComma';
162 }
163 else if (optsNone) {
164 missingDelimiter = true;
165 messageId = 'unexpectedSemi';
166 }
167 }
168 else if (lastToken.value === ',') {
169 if (optsSemi) {
170 messageId = 'expectedSemi';
171 }
172 else if (optsNone) {
173 missingDelimiter = true;
174 messageId = 'unexpectedComma';
175 }
176 }
177 else {
178 if (optsSemi) {
179 missingDelimiter = true;
180 messageId = 'expectedSemi';
181 }
182 else if (optsComma) {
183 missingDelimiter = true;
184 messageId = 'expectedComma';
185 }
186 }
187 if (messageId) {
188 context.report({
189 node: lastToken,
190 loc: {
191 start: {
192 line: lastToken.loc.end.line,
193 column: lastToken.loc.end.column,
194 },
195 end: {
196 line: lastToken.loc.end.line,
197 column: lastToken.loc.end.column,
198 },
199 },
200 messageId,
201 fix: makeFixFunction({
202 optsNone,
203 optsSemi,
204 lastToken,
205 missingDelimiter,
206 lastTokenLine,
207 isSingleLine: opts.type === 'single-line',
208 }),
209 });
210 }
211 }
212 /**
213 * Check the member separator being used matches the delimiter.
214 * @param {ASTNode} node the node to be evaluated.
215 */
216 function checkMemberSeparatorStyle(node) {
217 const members = node.type === utils_1.AST_NODE_TYPES.TSInterfaceBody ? node.body : node.members;
218 let isSingleLine = node.loc.start.line === node.loc.end.line;
219 if (options.multilineDetection === 'last-member' &&
220 !isSingleLine &&
221 members.length > 0) {
222 const lastMember = members[members.length - 1];
223 if (lastMember.loc.end.line === node.loc.end.line) {
224 isSingleLine = true;
225 }
226 }
227 const typeOpts = node.type === utils_1.AST_NODE_TYPES.TSInterfaceBody
228 ? interfaceOptions
229 : typeLiteralOptions;
230 const opts = isSingleLine
231 ? Object.assign(Object.assign({}, typeOpts.singleline), { type: 'single-line' }) : Object.assign(Object.assign({}, typeOpts.multiline), { type: 'multi-line' });
232 members.forEach((member, index) => {
233 checkLastToken(member, opts !== null && opts !== void 0 ? opts : {}, index === members.length - 1);
234 });
235 }
236 return {
237 TSInterfaceBody: checkMemberSeparatorStyle,
238 TSTypeLiteral: checkMemberSeparatorStyle,
239 };
240 },
241});
242//# sourceMappingURL=member-delimiter-style.js.map
\No newline at end of file