You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
246 lines
7.4 KiB
246 lines
7.4 KiB
"use strict"; |
|
|
|
Object.defineProperty(exports, "__esModule", { |
|
value: true |
|
}); |
|
exports.default = void 0; |
|
|
|
var _experimentalUtils = require("@typescript-eslint/experimental-utils"); |
|
|
|
var _utils = require("./utils"); |
|
|
|
const trimFXprefix = word => ['f', 'x'].includes(word.charAt(0)) ? word.substr(1) : word; |
|
|
|
const doesBinaryExpressionContainStringNode = binaryExp => { |
|
if ((0, _utils.isStringNode)(binaryExp.right)) { |
|
return true; |
|
} |
|
|
|
if (binaryExp.left.type === _experimentalUtils.AST_NODE_TYPES.BinaryExpression) { |
|
return doesBinaryExpressionContainStringNode(binaryExp.left); |
|
} |
|
|
|
return (0, _utils.isStringNode)(binaryExp.left); |
|
}; |
|
|
|
const quoteStringValue = node => node.type === _experimentalUtils.AST_NODE_TYPES.TemplateLiteral ? `\`${node.quasis[0].value.raw}\`` : node.raw; |
|
|
|
const compileMatcherPattern = matcherMaybeWithMessage => { |
|
const [matcher, message] = Array.isArray(matcherMaybeWithMessage) ? matcherMaybeWithMessage : [matcherMaybeWithMessage]; |
|
return [new RegExp(matcher, 'u'), message]; |
|
}; |
|
|
|
const compileMatcherPatterns = matchers => { |
|
if (typeof matchers === 'string' || Array.isArray(matchers)) { |
|
const compiledMatcher = compileMatcherPattern(matchers); |
|
return { |
|
describe: compiledMatcher, |
|
test: compiledMatcher, |
|
it: compiledMatcher |
|
}; |
|
} |
|
|
|
return { |
|
describe: matchers.describe ? compileMatcherPattern(matchers.describe) : null, |
|
test: matchers.test ? compileMatcherPattern(matchers.test) : null, |
|
it: matchers.it ? compileMatcherPattern(matchers.it) : null |
|
}; |
|
}; |
|
|
|
const MatcherAndMessageSchema = { |
|
type: 'array', |
|
items: { |
|
type: 'string' |
|
}, |
|
minItems: 1, |
|
maxItems: 2, |
|
additionalItems: false |
|
}; |
|
|
|
var _default = (0, _utils.createRule)({ |
|
name: __filename, |
|
meta: { |
|
docs: { |
|
category: 'Best Practices', |
|
description: 'Enforce valid titles', |
|
recommended: 'error' |
|
}, |
|
messages: { |
|
titleMustBeString: 'Title must be a string', |
|
emptyTitle: '{{ jestFunctionName }} should not have an empty title', |
|
duplicatePrefix: 'should not have duplicate prefix', |
|
accidentalSpace: 'should not have leading or trailing spaces', |
|
disallowedWord: '"{{ word }}" is not allowed in test titles.', |
|
mustNotMatch: '{{ jestFunctionName }} should not match {{ pattern }}', |
|
mustMatch: '{{ jestFunctionName }} should match {{ pattern }}', |
|
mustNotMatchCustom: '{{ message }}', |
|
mustMatchCustom: '{{ message }}' |
|
}, |
|
type: 'suggestion', |
|
schema: [{ |
|
type: 'object', |
|
properties: { |
|
ignoreTypeOfDescribeName: { |
|
type: 'boolean', |
|
default: false |
|
}, |
|
disallowedWords: { |
|
type: 'array', |
|
items: { |
|
type: 'string' |
|
} |
|
} |
|
}, |
|
patternProperties: { |
|
[/^must(?:Not)?Match$/u.source]: { |
|
oneOf: [{ |
|
type: 'string' |
|
}, MatcherAndMessageSchema, { |
|
type: 'object', |
|
propertyNames: { |
|
enum: ['describe', 'test', 'it'] |
|
}, |
|
additionalProperties: { |
|
oneOf: [{ |
|
type: 'string' |
|
}, MatcherAndMessageSchema] |
|
} |
|
}] |
|
} |
|
}, |
|
additionalProperties: false |
|
}], |
|
fixable: 'code' |
|
}, |
|
defaultOptions: [{ |
|
ignoreTypeOfDescribeName: false, |
|
disallowedWords: [] |
|
}], |
|
|
|
create(context, [{ |
|
ignoreTypeOfDescribeName, |
|
disallowedWords = [], |
|
mustNotMatch, |
|
mustMatch |
|
}]) { |
|
const disallowedWordsRegexp = new RegExp(`\\b(${disallowedWords.join('|')})\\b`, 'iu'); |
|
const mustNotMatchPatterns = compileMatcherPatterns(mustNotMatch !== null && mustNotMatch !== void 0 ? mustNotMatch : {}); |
|
const mustMatchPatterns = compileMatcherPatterns(mustMatch !== null && mustMatch !== void 0 ? mustMatch : {}); |
|
return { |
|
CallExpression(node) { |
|
var _mustNotMatchPatterns, _mustMatchPatterns$je; |
|
|
|
if (!(0, _utils.isDescribeCall)(node) && !(0, _utils.isTestCaseCall)(node)) { |
|
return; |
|
} |
|
|
|
const [argument] = node.arguments; |
|
|
|
if (!argument) { |
|
return; |
|
} |
|
|
|
if (!(0, _utils.isStringNode)(argument)) { |
|
if (argument.type === _experimentalUtils.AST_NODE_TYPES.BinaryExpression && doesBinaryExpressionContainStringNode(argument)) { |
|
return; |
|
} |
|
|
|
if (argument.type !== _experimentalUtils.AST_NODE_TYPES.TemplateLiteral && !(ignoreTypeOfDescribeName && (0, _utils.isDescribeCall)(node))) { |
|
context.report({ |
|
messageId: 'titleMustBeString', |
|
loc: argument.loc |
|
}); |
|
} |
|
|
|
return; |
|
} |
|
|
|
const title = (0, _utils.getStringValue)(argument); |
|
|
|
if (!title) { |
|
context.report({ |
|
messageId: 'emptyTitle', |
|
data: { |
|
jestFunctionName: (0, _utils.isDescribeCall)(node) ? _utils.DescribeAlias.describe : _utils.TestCaseName.test |
|
}, |
|
node |
|
}); |
|
return; |
|
} |
|
|
|
if (disallowedWords.length > 0) { |
|
const disallowedMatch = disallowedWordsRegexp.exec(title); |
|
|
|
if (disallowedMatch) { |
|
context.report({ |
|
data: { |
|
word: disallowedMatch[1] |
|
}, |
|
messageId: 'disallowedWord', |
|
node: argument |
|
}); |
|
return; |
|
} |
|
} |
|
|
|
if (title.trim().length !== title.length) { |
|
context.report({ |
|
messageId: 'accidentalSpace', |
|
node: argument, |
|
fix: fixer => [fixer.replaceTextRange(argument.range, quoteStringValue(argument).replace(/^([`'"]) +?/u, '$1').replace(/ +?([`'"])$/u, '$1'))] |
|
}); |
|
} |
|
|
|
const nodeName = trimFXprefix((0, _utils.getNodeName)(node)); |
|
const [firstWord] = title.split(' '); |
|
|
|
if (firstWord.toLowerCase() === nodeName) { |
|
context.report({ |
|
messageId: 'duplicatePrefix', |
|
node: argument, |
|
fix: fixer => [fixer.replaceTextRange(argument.range, quoteStringValue(argument).replace(/^([`'"]).+? /u, '$1'))] |
|
}); |
|
} |
|
|
|
const [jestFunctionName] = nodeName.split('.'); |
|
const [mustNotMatchPattern, mustNotMatchMessage] = (_mustNotMatchPatterns = mustNotMatchPatterns[jestFunctionName]) !== null && _mustNotMatchPatterns !== void 0 ? _mustNotMatchPatterns : []; |
|
|
|
if (mustNotMatchPattern) { |
|
if (mustNotMatchPattern.test(title)) { |
|
context.report({ |
|
messageId: mustNotMatchMessage ? 'mustNotMatchCustom' : 'mustNotMatch', |
|
node: argument, |
|
data: { |
|
jestFunctionName, |
|
pattern: mustNotMatchPattern, |
|
message: mustNotMatchMessage |
|
} |
|
}); |
|
return; |
|
} |
|
} |
|
|
|
const [mustMatchPattern, mustMatchMessage] = (_mustMatchPatterns$je = mustMatchPatterns[jestFunctionName]) !== null && _mustMatchPatterns$je !== void 0 ? _mustMatchPatterns$je : []; |
|
|
|
if (mustMatchPattern) { |
|
if (!mustMatchPattern.test(title)) { |
|
context.report({ |
|
messageId: mustMatchMessage ? 'mustMatchCustom' : 'mustMatch', |
|
node: argument, |
|
data: { |
|
jestFunctionName, |
|
pattern: mustMatchPattern, |
|
message: mustMatchMessage |
|
} |
|
}); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
}; |
|
} |
|
|
|
}); |
|
|
|
exports.default = _default; |