| [9af201e] | 1 | "use strict";
|
|---|
| 2 |
|
|---|
| 3 | Object.defineProperty(exports, "__esModule", {
|
|---|
| 4 | value: true
|
|---|
| 5 | });
|
|---|
| 6 | exports.default = void 0;
|
|---|
| 7 |
|
|---|
| 8 | var _experimentalUtils = require("@typescript-eslint/experimental-utils");
|
|---|
| 9 |
|
|---|
| 10 | var _utils = require("./utils");
|
|---|
| 11 |
|
|---|
| 12 | const trimFXprefix = word => ['f', 'x'].includes(word.charAt(0)) ? word.substr(1) : word;
|
|---|
| 13 |
|
|---|
| 14 | const doesBinaryExpressionContainStringNode = binaryExp => {
|
|---|
| 15 | if ((0, _utils.isStringNode)(binaryExp.right)) {
|
|---|
| 16 | return true;
|
|---|
| 17 | }
|
|---|
| 18 |
|
|---|
| 19 | if (binaryExp.left.type === _experimentalUtils.AST_NODE_TYPES.BinaryExpression) {
|
|---|
| 20 | return doesBinaryExpressionContainStringNode(binaryExp.left);
|
|---|
| 21 | }
|
|---|
| 22 |
|
|---|
| 23 | return (0, _utils.isStringNode)(binaryExp.left);
|
|---|
| 24 | };
|
|---|
| 25 |
|
|---|
| 26 | const quoteStringValue = node => node.type === _experimentalUtils.AST_NODE_TYPES.TemplateLiteral ? `\`${node.quasis[0].value.raw}\`` : node.raw;
|
|---|
| 27 |
|
|---|
| 28 | const compileMatcherPattern = matcherMaybeWithMessage => {
|
|---|
| 29 | const [matcher, message] = Array.isArray(matcherMaybeWithMessage) ? matcherMaybeWithMessage : [matcherMaybeWithMessage];
|
|---|
| 30 | return [new RegExp(matcher, 'u'), message];
|
|---|
| 31 | };
|
|---|
| 32 |
|
|---|
| 33 | const compileMatcherPatterns = matchers => {
|
|---|
| 34 | if (typeof matchers === 'string' || Array.isArray(matchers)) {
|
|---|
| 35 | const compiledMatcher = compileMatcherPattern(matchers);
|
|---|
| 36 | return {
|
|---|
| 37 | describe: compiledMatcher,
|
|---|
| 38 | test: compiledMatcher,
|
|---|
| 39 | it: compiledMatcher
|
|---|
| 40 | };
|
|---|
| 41 | }
|
|---|
| 42 |
|
|---|
| 43 | return {
|
|---|
| 44 | describe: matchers.describe ? compileMatcherPattern(matchers.describe) : null,
|
|---|
| 45 | test: matchers.test ? compileMatcherPattern(matchers.test) : null,
|
|---|
| 46 | it: matchers.it ? compileMatcherPattern(matchers.it) : null
|
|---|
| 47 | };
|
|---|
| 48 | };
|
|---|
| 49 |
|
|---|
| 50 | const MatcherAndMessageSchema = {
|
|---|
| 51 | type: 'array',
|
|---|
| 52 | items: {
|
|---|
| 53 | type: 'string'
|
|---|
| 54 | },
|
|---|
| 55 | minItems: 1,
|
|---|
| 56 | maxItems: 2,
|
|---|
| 57 | additionalItems: false
|
|---|
| 58 | };
|
|---|
| 59 |
|
|---|
| 60 | var _default = (0, _utils.createRule)({
|
|---|
| 61 | name: __filename,
|
|---|
| 62 | meta: {
|
|---|
| 63 | docs: {
|
|---|
| 64 | category: 'Best Practices',
|
|---|
| 65 | description: 'Enforce valid titles',
|
|---|
| 66 | recommended: 'error'
|
|---|
| 67 | },
|
|---|
| 68 | messages: {
|
|---|
| 69 | titleMustBeString: 'Title must be a string',
|
|---|
| 70 | emptyTitle: '{{ jestFunctionName }} should not have an empty title',
|
|---|
| 71 | duplicatePrefix: 'should not have duplicate prefix',
|
|---|
| 72 | accidentalSpace: 'should not have leading or trailing spaces',
|
|---|
| 73 | disallowedWord: '"{{ word }}" is not allowed in test titles.',
|
|---|
| 74 | mustNotMatch: '{{ jestFunctionName }} should not match {{ pattern }}',
|
|---|
| 75 | mustMatch: '{{ jestFunctionName }} should match {{ pattern }}',
|
|---|
| 76 | mustNotMatchCustom: '{{ message }}',
|
|---|
| 77 | mustMatchCustom: '{{ message }}'
|
|---|
| 78 | },
|
|---|
| 79 | type: 'suggestion',
|
|---|
| 80 | schema: [{
|
|---|
| 81 | type: 'object',
|
|---|
| 82 | properties: {
|
|---|
| 83 | ignoreTypeOfDescribeName: {
|
|---|
| 84 | type: 'boolean',
|
|---|
| 85 | default: false
|
|---|
| 86 | },
|
|---|
| 87 | disallowedWords: {
|
|---|
| 88 | type: 'array',
|
|---|
| 89 | items: {
|
|---|
| 90 | type: 'string'
|
|---|
| 91 | }
|
|---|
| 92 | }
|
|---|
| 93 | },
|
|---|
| 94 | patternProperties: {
|
|---|
| 95 | [/^must(?:Not)?Match$/u.source]: {
|
|---|
| 96 | oneOf: [{
|
|---|
| 97 | type: 'string'
|
|---|
| 98 | }, MatcherAndMessageSchema, {
|
|---|
| 99 | type: 'object',
|
|---|
| 100 | propertyNames: {
|
|---|
| 101 | enum: ['describe', 'test', 'it']
|
|---|
| 102 | },
|
|---|
| 103 | additionalProperties: {
|
|---|
| 104 | oneOf: [{
|
|---|
| 105 | type: 'string'
|
|---|
| 106 | }, MatcherAndMessageSchema]
|
|---|
| 107 | }
|
|---|
| 108 | }]
|
|---|
| 109 | }
|
|---|
| 110 | },
|
|---|
| 111 | additionalProperties: false
|
|---|
| 112 | }],
|
|---|
| 113 | fixable: 'code'
|
|---|
| 114 | },
|
|---|
| 115 | defaultOptions: [{
|
|---|
| 116 | ignoreTypeOfDescribeName: false,
|
|---|
| 117 | disallowedWords: []
|
|---|
| 118 | }],
|
|---|
| 119 |
|
|---|
| 120 | create(context, [{
|
|---|
| 121 | ignoreTypeOfDescribeName,
|
|---|
| 122 | disallowedWords = [],
|
|---|
| 123 | mustNotMatch,
|
|---|
| 124 | mustMatch
|
|---|
| 125 | }]) {
|
|---|
| 126 | const disallowedWordsRegexp = new RegExp(`\\b(${disallowedWords.join('|')})\\b`, 'iu');
|
|---|
| 127 | const mustNotMatchPatterns = compileMatcherPatterns(mustNotMatch !== null && mustNotMatch !== void 0 ? mustNotMatch : {});
|
|---|
| 128 | const mustMatchPatterns = compileMatcherPatterns(mustMatch !== null && mustMatch !== void 0 ? mustMatch : {});
|
|---|
| 129 | return {
|
|---|
| 130 | CallExpression(node) {
|
|---|
| 131 | var _mustNotMatchPatterns, _mustMatchPatterns$je;
|
|---|
| 132 |
|
|---|
| 133 | if (!(0, _utils.isDescribeCall)(node) && !(0, _utils.isTestCaseCall)(node)) {
|
|---|
| 134 | return;
|
|---|
| 135 | }
|
|---|
| 136 |
|
|---|
| 137 | const [argument] = node.arguments;
|
|---|
| 138 |
|
|---|
| 139 | if (!argument) {
|
|---|
| 140 | return;
|
|---|
| 141 | }
|
|---|
| 142 |
|
|---|
| 143 | if (!(0, _utils.isStringNode)(argument)) {
|
|---|
| 144 | if (argument.type === _experimentalUtils.AST_NODE_TYPES.BinaryExpression && doesBinaryExpressionContainStringNode(argument)) {
|
|---|
| 145 | return;
|
|---|
| 146 | }
|
|---|
| 147 |
|
|---|
| 148 | if (argument.type !== _experimentalUtils.AST_NODE_TYPES.TemplateLiteral && !(ignoreTypeOfDescribeName && (0, _utils.isDescribeCall)(node))) {
|
|---|
| 149 | context.report({
|
|---|
| 150 | messageId: 'titleMustBeString',
|
|---|
| 151 | loc: argument.loc
|
|---|
| 152 | });
|
|---|
| 153 | }
|
|---|
| 154 |
|
|---|
| 155 | return;
|
|---|
| 156 | }
|
|---|
| 157 |
|
|---|
| 158 | const title = (0, _utils.getStringValue)(argument);
|
|---|
| 159 |
|
|---|
| 160 | if (!title) {
|
|---|
| 161 | context.report({
|
|---|
| 162 | messageId: 'emptyTitle',
|
|---|
| 163 | data: {
|
|---|
| 164 | jestFunctionName: (0, _utils.isDescribeCall)(node) ? _utils.DescribeAlias.describe : _utils.TestCaseName.test
|
|---|
| 165 | },
|
|---|
| 166 | node
|
|---|
| 167 | });
|
|---|
| 168 | return;
|
|---|
| 169 | }
|
|---|
| 170 |
|
|---|
| 171 | if (disallowedWords.length > 0) {
|
|---|
| 172 | const disallowedMatch = disallowedWordsRegexp.exec(title);
|
|---|
| 173 |
|
|---|
| 174 | if (disallowedMatch) {
|
|---|
| 175 | context.report({
|
|---|
| 176 | data: {
|
|---|
| 177 | word: disallowedMatch[1]
|
|---|
| 178 | },
|
|---|
| 179 | messageId: 'disallowedWord',
|
|---|
| 180 | node: argument
|
|---|
| 181 | });
|
|---|
| 182 | return;
|
|---|
| 183 | }
|
|---|
| 184 | }
|
|---|
| 185 |
|
|---|
| 186 | if (title.trim().length !== title.length) {
|
|---|
| 187 | context.report({
|
|---|
| 188 | messageId: 'accidentalSpace',
|
|---|
| 189 | node: argument,
|
|---|
| 190 | fix: fixer => [fixer.replaceTextRange(argument.range, quoteStringValue(argument).replace(/^([`'"]) +?/u, '$1').replace(/ +?([`'"])$/u, '$1'))]
|
|---|
| 191 | });
|
|---|
| 192 | }
|
|---|
| 193 |
|
|---|
| 194 | const nodeName = trimFXprefix((0, _utils.getNodeName)(node));
|
|---|
| 195 | const [firstWord] = title.split(' ');
|
|---|
| 196 |
|
|---|
| 197 | if (firstWord.toLowerCase() === nodeName) {
|
|---|
| 198 | context.report({
|
|---|
| 199 | messageId: 'duplicatePrefix',
|
|---|
| 200 | node: argument,
|
|---|
| 201 | fix: fixer => [fixer.replaceTextRange(argument.range, quoteStringValue(argument).replace(/^([`'"]).+? /u, '$1'))]
|
|---|
| 202 | });
|
|---|
| 203 | }
|
|---|
| 204 |
|
|---|
| 205 | const [jestFunctionName] = nodeName.split('.');
|
|---|
| 206 | const [mustNotMatchPattern, mustNotMatchMessage] = (_mustNotMatchPatterns = mustNotMatchPatterns[jestFunctionName]) !== null && _mustNotMatchPatterns !== void 0 ? _mustNotMatchPatterns : [];
|
|---|
| 207 |
|
|---|
| 208 | if (mustNotMatchPattern) {
|
|---|
| 209 | if (mustNotMatchPattern.test(title)) {
|
|---|
| 210 | context.report({
|
|---|
| 211 | messageId: mustNotMatchMessage ? 'mustNotMatchCustom' : 'mustNotMatch',
|
|---|
| 212 | node: argument,
|
|---|
| 213 | data: {
|
|---|
| 214 | jestFunctionName,
|
|---|
| 215 | pattern: mustNotMatchPattern,
|
|---|
| 216 | message: mustNotMatchMessage
|
|---|
| 217 | }
|
|---|
| 218 | });
|
|---|
| 219 | return;
|
|---|
| 220 | }
|
|---|
| 221 | }
|
|---|
| 222 |
|
|---|
| 223 | const [mustMatchPattern, mustMatchMessage] = (_mustMatchPatterns$je = mustMatchPatterns[jestFunctionName]) !== null && _mustMatchPatterns$je !== void 0 ? _mustMatchPatterns$je : [];
|
|---|
| 224 |
|
|---|
| 225 | if (mustMatchPattern) {
|
|---|
| 226 | if (!mustMatchPattern.test(title)) {
|
|---|
| 227 | context.report({
|
|---|
| 228 | messageId: mustMatchMessage ? 'mustMatchCustom' : 'mustMatch',
|
|---|
| 229 | node: argument,
|
|---|
| 230 | data: {
|
|---|
| 231 | jestFunctionName,
|
|---|
| 232 | pattern: mustMatchPattern,
|
|---|
| 233 | message: mustMatchMessage
|
|---|
| 234 | }
|
|---|
| 235 | });
|
|---|
| 236 | return;
|
|---|
| 237 | }
|
|---|
| 238 | }
|
|---|
| 239 | }
|
|---|
| 240 |
|
|---|
| 241 | };
|
|---|
| 242 | }
|
|---|
| 243 |
|
|---|
| 244 | });
|
|---|
| 245 |
|
|---|
| 246 | exports.default = _default; |
|---|