| 1 | "use strict";
|
|---|
| 2 |
|
|---|
| 3 | Object.defineProperty(exports, "__esModule", {
|
|---|
| 4 | value: true
|
|---|
| 5 | });
|
|---|
| 6 | exports.getAccessorValue = exports.followTypeAssertionChain = exports.createRule = exports.TestCaseProperty = exports.TestCaseName = exports.ModifierName = exports.HookName = exports.EqualityMatcher = exports.DescribeProperty = exports.DescribeAlias = void 0;
|
|---|
| 7 | exports.getNodeName = getNodeName;
|
|---|
| 8 | exports.scopeHasLocalReference = exports.parseExpectCall = exports.isTestCaseCall = exports.isSupportedAccessor = exports.isStringNode = exports.isParsedEqualityMatcherCall = exports.isIdentifier = exports.isHook = exports.isFunction = exports.isExpectMember = exports.isExpectCall = exports.isDescribeCall = exports.hasOnlyOneArgument = exports.getTestCallExpressionsFromDeclaredVariables = exports.getStringValue = void 0;
|
|---|
| 9 |
|
|---|
| 10 | var _path = require("path");
|
|---|
| 11 |
|
|---|
| 12 | var _experimentalUtils = require("@typescript-eslint/experimental-utils");
|
|---|
| 13 |
|
|---|
| 14 | var _package = require("../../package.json");
|
|---|
| 15 |
|
|---|
| 16 | const REPO_URL = 'https://github.com/jest-community/eslint-plugin-jest';
|
|---|
| 17 |
|
|---|
| 18 | const createRule = _experimentalUtils.ESLintUtils.RuleCreator(name => {
|
|---|
| 19 | const ruleName = (0, _path.parse)(name).name;
|
|---|
| 20 | return `${REPO_URL}/blob/v${_package.version}/docs/rules/${ruleName}.md`;
|
|---|
| 21 | });
|
|---|
| 22 |
|
|---|
| 23 | exports.createRule = createRule;
|
|---|
| 24 |
|
|---|
| 25 | const isTypeCastExpression = node => node.type === _experimentalUtils.AST_NODE_TYPES.TSAsExpression || node.type === _experimentalUtils.AST_NODE_TYPES.TSTypeAssertion;
|
|---|
| 26 |
|
|---|
| 27 | const followTypeAssertionChain = expression => isTypeCastExpression(expression) ? followTypeAssertionChain(expression.expression) : expression;
|
|---|
| 28 | /**
|
|---|
| 29 | * A `Literal` with a `value` of type `string`.
|
|---|
| 30 | */
|
|---|
| 31 |
|
|---|
| 32 |
|
|---|
| 33 | exports.followTypeAssertionChain = followTypeAssertionChain;
|
|---|
| 34 |
|
|---|
| 35 | /**
|
|---|
| 36 | * Checks if the given `node` is a `StringLiteral`.
|
|---|
| 37 | *
|
|---|
| 38 | * If a `value` is provided & the `node` is a `StringLiteral`,
|
|---|
| 39 | * the `value` will be compared to that of the `StringLiteral`.
|
|---|
| 40 | *
|
|---|
| 41 | * @param {Node} node
|
|---|
| 42 | * @param {V} [value]
|
|---|
| 43 | *
|
|---|
| 44 | * @return {node is StringLiteral<V>}
|
|---|
| 45 | *
|
|---|
| 46 | * @template V
|
|---|
| 47 | */
|
|---|
| 48 | const isStringLiteral = (node, value) => node.type === _experimentalUtils.AST_NODE_TYPES.Literal && typeof node.value === 'string' && (value === undefined || node.value === value);
|
|---|
| 49 |
|
|---|
| 50 | /**
|
|---|
| 51 | * Checks if the given `node` is a `TemplateLiteral`.
|
|---|
| 52 | *
|
|---|
| 53 | * Complex `TemplateLiteral`s are not considered specific, and so will return `false`.
|
|---|
| 54 | *
|
|---|
| 55 | * If a `value` is provided & the `node` is a `TemplateLiteral`,
|
|---|
| 56 | * the `value` will be compared to that of the `TemplateLiteral`.
|
|---|
| 57 | *
|
|---|
| 58 | * @param {Node} node
|
|---|
| 59 | * @param {V} [value]
|
|---|
| 60 | *
|
|---|
| 61 | * @return {node is TemplateLiteral<V>}
|
|---|
| 62 | *
|
|---|
| 63 | * @template V
|
|---|
| 64 | */
|
|---|
| 65 | const isTemplateLiteral = (node, value) => node.type === _experimentalUtils.AST_NODE_TYPES.TemplateLiteral && node.quasis.length === 1 && ( // bail out if not simple
|
|---|
| 66 | value === undefined || node.quasis[0].value.raw === value);
|
|---|
| 67 |
|
|---|
| 68 | /**
|
|---|
| 69 | * Checks if the given `node` is a {@link StringNode}.
|
|---|
| 70 | *
|
|---|
| 71 | * @param {Node} node
|
|---|
| 72 | * @param {V} [specifics]
|
|---|
| 73 | *
|
|---|
| 74 | * @return {node is StringNode}
|
|---|
| 75 | *
|
|---|
| 76 | * @template V
|
|---|
| 77 | */
|
|---|
| 78 | const isStringNode = (node, specifics) => isStringLiteral(node, specifics) || isTemplateLiteral(node, specifics);
|
|---|
| 79 | /**
|
|---|
| 80 | * Gets the value of the given `StringNode`.
|
|---|
| 81 | *
|
|---|
| 82 | * If the `node` is a `TemplateLiteral`, the `raw` value is used;
|
|---|
| 83 | * otherwise, `value` is returned instead.
|
|---|
| 84 | *
|
|---|
| 85 | * @param {StringNode<S>} node
|
|---|
| 86 | *
|
|---|
| 87 | * @return {S}
|
|---|
| 88 | *
|
|---|
| 89 | * @template S
|
|---|
| 90 | */
|
|---|
| 91 |
|
|---|
| 92 |
|
|---|
| 93 | exports.isStringNode = isStringNode;
|
|---|
| 94 |
|
|---|
| 95 | const getStringValue = node => isTemplateLiteral(node) ? node.quasis[0].value.raw : node.value;
|
|---|
| 96 | /**
|
|---|
| 97 | * Represents a `MemberExpression` with a "known" `property`.
|
|---|
| 98 | */
|
|---|
| 99 |
|
|---|
| 100 |
|
|---|
| 101 | exports.getStringValue = getStringValue;
|
|---|
| 102 |
|
|---|
| 103 | /**
|
|---|
| 104 | * Guards that the given `call` has only one `argument`.
|
|---|
| 105 | *
|
|---|
| 106 | * @param {CallExpression} call
|
|---|
| 107 | *
|
|---|
| 108 | * @return {call is CallExpressionWithSingleArgument}
|
|---|
| 109 | */
|
|---|
| 110 | const hasOnlyOneArgument = call => call.arguments.length === 1;
|
|---|
| 111 | /**
|
|---|
| 112 | * An `Identifier` with a known `name` value - i.e `expect`.
|
|---|
| 113 | */
|
|---|
| 114 |
|
|---|
| 115 |
|
|---|
| 116 | exports.hasOnlyOneArgument = hasOnlyOneArgument;
|
|---|
| 117 |
|
|---|
| 118 | /**
|
|---|
| 119 | * Checks if the given `node` is an `Identifier`.
|
|---|
| 120 | *
|
|---|
| 121 | * If a `name` is provided, & the `node` is an `Identifier`,
|
|---|
| 122 | * the `name` will be compared to that of the `identifier`.
|
|---|
| 123 | *
|
|---|
| 124 | * @param {Node} node
|
|---|
| 125 | * @param {V} [name]
|
|---|
| 126 | *
|
|---|
| 127 | * @return {node is KnownIdentifier<Name>}
|
|---|
| 128 | *
|
|---|
| 129 | * @template V
|
|---|
| 130 | */
|
|---|
| 131 | const isIdentifier = (node, name) => node.type === _experimentalUtils.AST_NODE_TYPES.Identifier && (name === undefined || node.name === name);
|
|---|
| 132 | /**
|
|---|
| 133 | * Checks if the given `node` is a "supported accessor".
|
|---|
| 134 | *
|
|---|
| 135 | * This means that it's a node can be used to access properties,
|
|---|
| 136 | * and who's "value" can be statically determined.
|
|---|
| 137 | *
|
|---|
| 138 | * `MemberExpression` nodes most commonly contain accessors,
|
|---|
| 139 | * but it's possible for other nodes to contain them.
|
|---|
| 140 | *
|
|---|
| 141 | * If a `value` is provided & the `node` is an `AccessorNode`,
|
|---|
| 142 | * the `value` will be compared to that of the `AccessorNode`.
|
|---|
| 143 | *
|
|---|
| 144 | * Note that `value` here refers to the normalised value.
|
|---|
| 145 | * The property that holds the value is not always called `name`.
|
|---|
| 146 | *
|
|---|
| 147 | * @param {Node} node
|
|---|
| 148 | * @param {V} [value]
|
|---|
| 149 | *
|
|---|
| 150 | * @return {node is AccessorNode<V>}
|
|---|
| 151 | *
|
|---|
| 152 | * @template V
|
|---|
| 153 | */
|
|---|
| 154 |
|
|---|
| 155 |
|
|---|
| 156 | exports.isIdentifier = isIdentifier;
|
|---|
| 157 |
|
|---|
| 158 | const isSupportedAccessor = (node, value) => isIdentifier(node, value) || isStringNode(node, value);
|
|---|
| 159 | /**
|
|---|
| 160 | * Gets the value of the given `AccessorNode`,
|
|---|
| 161 | * account for the different node types.
|
|---|
| 162 | *
|
|---|
| 163 | * @param {AccessorNode<S>} accessor
|
|---|
| 164 | *
|
|---|
| 165 | * @return {S}
|
|---|
| 166 | *
|
|---|
| 167 | * @template S
|
|---|
| 168 | */
|
|---|
| 169 |
|
|---|
| 170 |
|
|---|
| 171 | exports.isSupportedAccessor = isSupportedAccessor;
|
|---|
| 172 |
|
|---|
| 173 | const getAccessorValue = accessor => accessor.type === _experimentalUtils.AST_NODE_TYPES.Identifier ? accessor.name : getStringValue(accessor);
|
|---|
| 174 |
|
|---|
| 175 | exports.getAccessorValue = getAccessorValue;
|
|---|
| 176 |
|
|---|
| 177 | /**
|
|---|
| 178 | * Checks if the given `node` is a valid `ExpectCall`.
|
|---|
| 179 | *
|
|---|
| 180 | * In order to be an `ExpectCall`, the `node` must:
|
|---|
| 181 | * * be a `CallExpression`,
|
|---|
| 182 | * * have an accessor named 'expect',
|
|---|
| 183 | * * have a `parent`.
|
|---|
| 184 | *
|
|---|
| 185 | * @param {Node} node
|
|---|
| 186 | *
|
|---|
| 187 | * @return {node is ExpectCall}
|
|---|
| 188 | */
|
|---|
| 189 | const isExpectCall = node => node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && isSupportedAccessor(node.callee, 'expect') && node.parent !== undefined;
|
|---|
| 190 |
|
|---|
| 191 | exports.isExpectCall = isExpectCall;
|
|---|
| 192 |
|
|---|
| 193 | const isExpectMember = (node, name) => node.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && isSupportedAccessor(node.property, name);
|
|---|
| 194 | /**
|
|---|
| 195 | * Represents all the jest matchers.
|
|---|
| 196 | */
|
|---|
| 197 |
|
|---|
| 198 |
|
|---|
| 199 | exports.isExpectMember = isExpectMember;
|
|---|
| 200 | let ModifierName;
|
|---|
| 201 | exports.ModifierName = ModifierName;
|
|---|
| 202 |
|
|---|
| 203 | (function (ModifierName) {
|
|---|
| 204 | ModifierName["not"] = "not";
|
|---|
| 205 | ModifierName["rejects"] = "rejects";
|
|---|
| 206 | ModifierName["resolves"] = "resolves";
|
|---|
| 207 | })(ModifierName || (exports.ModifierName = ModifierName = {}));
|
|---|
| 208 |
|
|---|
| 209 | let EqualityMatcher;
|
|---|
| 210 | exports.EqualityMatcher = EqualityMatcher;
|
|---|
| 211 |
|
|---|
| 212 | (function (EqualityMatcher) {
|
|---|
| 213 | EqualityMatcher["toBe"] = "toBe";
|
|---|
| 214 | EqualityMatcher["toEqual"] = "toEqual";
|
|---|
| 215 | EqualityMatcher["toStrictEqual"] = "toStrictEqual";
|
|---|
| 216 | })(EqualityMatcher || (exports.EqualityMatcher = EqualityMatcher = {}));
|
|---|
| 217 |
|
|---|
| 218 | const isParsedEqualityMatcherCall = (matcher, name) => (name ? matcher.name === name : EqualityMatcher.hasOwnProperty(matcher.name)) && matcher.arguments !== null && matcher.arguments.length === 1;
|
|---|
| 219 | /**
|
|---|
| 220 | * Represents a parsed expect matcher, such as `toBe`, `toContain`, and so on.
|
|---|
| 221 | */
|
|---|
| 222 |
|
|---|
| 223 |
|
|---|
| 224 | exports.isParsedEqualityMatcherCall = isParsedEqualityMatcherCall;
|
|---|
| 225 |
|
|---|
| 226 | const parseExpectMember = expectMember => ({
|
|---|
| 227 | name: getAccessorValue(expectMember.property),
|
|---|
| 228 | node: expectMember
|
|---|
| 229 | });
|
|---|
| 230 |
|
|---|
| 231 | const reparseAsMatcher = parsedMember => ({ ...parsedMember,
|
|---|
| 232 |
|
|---|
| 233 | /**
|
|---|
| 234 | * The arguments being passed to this `Matcher`, if any.
|
|---|
| 235 | *
|
|---|
| 236 | * If this matcher isn't called, this will be `null`.
|
|---|
| 237 | */
|
|---|
| 238 | arguments: parsedMember.node.parent.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? parsedMember.node.parent.arguments : null
|
|---|
| 239 | });
|
|---|
| 240 | /**
|
|---|
| 241 | * Re-parses the given `parsedMember` as a `ParsedExpectModifier`.
|
|---|
| 242 | *
|
|---|
| 243 | * If the given `parsedMember` does not have a `name` of a valid `Modifier`,
|
|---|
| 244 | * an exception will be thrown.
|
|---|
| 245 | *
|
|---|
| 246 | * @param {ParsedExpectMember<ModifierName>} parsedMember
|
|---|
| 247 | *
|
|---|
| 248 | * @return {ParsedExpectModifier}
|
|---|
| 249 | */
|
|---|
| 250 |
|
|---|
| 251 |
|
|---|
| 252 | const reparseMemberAsModifier = parsedMember => {
|
|---|
| 253 | if (isSpecificMember(parsedMember, ModifierName.not)) {
|
|---|
| 254 | return parsedMember;
|
|---|
| 255 | }
|
|---|
| 256 | /* istanbul ignore if */
|
|---|
| 257 |
|
|---|
| 258 |
|
|---|
| 259 | if (!isSpecificMember(parsedMember, ModifierName.resolves) && !isSpecificMember(parsedMember, ModifierName.rejects)) {
|
|---|
| 260 | // ts doesn't think that the ModifierName.not check is the direct inverse as the above two checks
|
|---|
| 261 | // todo: impossible at runtime, but can't be typed w/o negation support
|
|---|
| 262 | throw new Error(`modifier name must be either "${ModifierName.resolves}" or "${ModifierName.rejects}" (got "${parsedMember.name}")`);
|
|---|
| 263 | }
|
|---|
| 264 |
|
|---|
| 265 | const negation = isExpectMember(parsedMember.node.parent, ModifierName.not) ? parsedMember.node.parent : undefined;
|
|---|
| 266 | return { ...parsedMember,
|
|---|
| 267 | negation
|
|---|
| 268 | };
|
|---|
| 269 | };
|
|---|
| 270 |
|
|---|
| 271 | const isSpecificMember = (member, specific) => member.name === specific;
|
|---|
| 272 | /**
|
|---|
| 273 | * Checks if the given `ParsedExpectMember` should be re-parsed as an `ParsedExpectModifier`.
|
|---|
| 274 | *
|
|---|
| 275 | * @param {ParsedExpectMember} member
|
|---|
| 276 | *
|
|---|
| 277 | * @return {member is ParsedExpectMember<ModifierName>}
|
|---|
| 278 | */
|
|---|
| 279 |
|
|---|
| 280 |
|
|---|
| 281 | const shouldBeParsedExpectModifier = member => ModifierName.hasOwnProperty(member.name);
|
|---|
| 282 |
|
|---|
| 283 | const parseExpectCall = expect => {
|
|---|
| 284 | const expectation = {
|
|---|
| 285 | expect
|
|---|
| 286 | };
|
|---|
| 287 |
|
|---|
| 288 | if (!isExpectMember(expect.parent)) {
|
|---|
| 289 | return expectation;
|
|---|
| 290 | }
|
|---|
| 291 |
|
|---|
| 292 | const parsedMember = parseExpectMember(expect.parent);
|
|---|
| 293 |
|
|---|
| 294 | if (!shouldBeParsedExpectModifier(parsedMember)) {
|
|---|
| 295 | expectation.matcher = reparseAsMatcher(parsedMember);
|
|---|
| 296 | return expectation;
|
|---|
| 297 | }
|
|---|
| 298 |
|
|---|
| 299 | const modifier = expectation.modifier = reparseMemberAsModifier(parsedMember);
|
|---|
| 300 | const memberNode = modifier.negation || modifier.node;
|
|---|
| 301 |
|
|---|
| 302 | if (!isExpectMember(memberNode.parent)) {
|
|---|
| 303 | return expectation;
|
|---|
| 304 | }
|
|---|
| 305 |
|
|---|
| 306 | expectation.matcher = reparseAsMatcher(parseExpectMember(memberNode.parent));
|
|---|
| 307 | return expectation;
|
|---|
| 308 | };
|
|---|
| 309 |
|
|---|
| 310 | exports.parseExpectCall = parseExpectCall;
|
|---|
| 311 | let DescribeAlias;
|
|---|
| 312 | exports.DescribeAlias = DescribeAlias;
|
|---|
| 313 |
|
|---|
| 314 | (function (DescribeAlias) {
|
|---|
| 315 | DescribeAlias["describe"] = "describe";
|
|---|
| 316 | DescribeAlias["fdescribe"] = "fdescribe";
|
|---|
| 317 | DescribeAlias["xdescribe"] = "xdescribe";
|
|---|
| 318 | })(DescribeAlias || (exports.DescribeAlias = DescribeAlias = {}));
|
|---|
| 319 |
|
|---|
| 320 | let TestCaseName;
|
|---|
| 321 | exports.TestCaseName = TestCaseName;
|
|---|
| 322 |
|
|---|
| 323 | (function (TestCaseName) {
|
|---|
| 324 | TestCaseName["fit"] = "fit";
|
|---|
| 325 | TestCaseName["it"] = "it";
|
|---|
| 326 | TestCaseName["test"] = "test";
|
|---|
| 327 | TestCaseName["xit"] = "xit";
|
|---|
| 328 | TestCaseName["xtest"] = "xtest";
|
|---|
| 329 | })(TestCaseName || (exports.TestCaseName = TestCaseName = {}));
|
|---|
| 330 |
|
|---|
| 331 | let HookName;
|
|---|
| 332 | exports.HookName = HookName;
|
|---|
| 333 |
|
|---|
| 334 | (function (HookName) {
|
|---|
| 335 | HookName["beforeAll"] = "beforeAll";
|
|---|
| 336 | HookName["beforeEach"] = "beforeEach";
|
|---|
| 337 | HookName["afterAll"] = "afterAll";
|
|---|
| 338 | HookName["afterEach"] = "afterEach";
|
|---|
| 339 | })(HookName || (exports.HookName = HookName = {}));
|
|---|
| 340 |
|
|---|
| 341 | let DescribeProperty;
|
|---|
| 342 | exports.DescribeProperty = DescribeProperty;
|
|---|
| 343 |
|
|---|
| 344 | (function (DescribeProperty) {
|
|---|
| 345 | DescribeProperty["each"] = "each";
|
|---|
| 346 | DescribeProperty["only"] = "only";
|
|---|
| 347 | DescribeProperty["skip"] = "skip";
|
|---|
| 348 | })(DescribeProperty || (exports.DescribeProperty = DescribeProperty = {}));
|
|---|
| 349 |
|
|---|
| 350 | let TestCaseProperty;
|
|---|
| 351 | exports.TestCaseProperty = TestCaseProperty;
|
|---|
| 352 |
|
|---|
| 353 | (function (TestCaseProperty) {
|
|---|
| 354 | TestCaseProperty["each"] = "each";
|
|---|
| 355 | TestCaseProperty["concurrent"] = "concurrent";
|
|---|
| 356 | TestCaseProperty["only"] = "only";
|
|---|
| 357 | TestCaseProperty["skip"] = "skip";
|
|---|
| 358 | TestCaseProperty["todo"] = "todo";
|
|---|
| 359 | })(TestCaseProperty || (exports.TestCaseProperty = TestCaseProperty = {}));
|
|---|
| 360 |
|
|---|
| 361 | const joinNames = (a, b) => a && b ? `${a}.${b}` : null;
|
|---|
| 362 |
|
|---|
| 363 | function getNodeName(node) {
|
|---|
| 364 | if (isSupportedAccessor(node)) {
|
|---|
| 365 | return getAccessorValue(node);
|
|---|
| 366 | }
|
|---|
| 367 |
|
|---|
| 368 | switch (node.type) {
|
|---|
| 369 | case _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression:
|
|---|
| 370 | return getNodeName(node.tag);
|
|---|
| 371 |
|
|---|
| 372 | case _experimentalUtils.AST_NODE_TYPES.MemberExpression:
|
|---|
| 373 | return joinNames(getNodeName(node.object), getNodeName(node.property));
|
|---|
| 374 |
|
|---|
| 375 | case _experimentalUtils.AST_NODE_TYPES.NewExpression:
|
|---|
| 376 | case _experimentalUtils.AST_NODE_TYPES.CallExpression:
|
|---|
| 377 | return getNodeName(node.callee);
|
|---|
| 378 | }
|
|---|
| 379 |
|
|---|
| 380 | return null;
|
|---|
| 381 | }
|
|---|
| 382 |
|
|---|
| 383 | const isFunction = node => node.type === _experimentalUtils.AST_NODE_TYPES.FunctionExpression || node.type === _experimentalUtils.AST_NODE_TYPES.ArrowFunctionExpression;
|
|---|
| 384 |
|
|---|
| 385 | exports.isFunction = isFunction;
|
|---|
| 386 |
|
|---|
| 387 | const isHook = node => node.callee.type === _experimentalUtils.AST_NODE_TYPES.Identifier && HookName.hasOwnProperty(node.callee.name);
|
|---|
| 388 |
|
|---|
| 389 | exports.isHook = isHook;
|
|---|
| 390 |
|
|---|
| 391 | const getTestCallExpressionsFromDeclaredVariables = declaredVariables => {
|
|---|
| 392 | return declaredVariables.reduce((acc, {
|
|---|
| 393 | references
|
|---|
| 394 | }) => acc.concat(references.map(({
|
|---|
| 395 | identifier
|
|---|
| 396 | }) => identifier.parent).filter(node => !!node && node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && isTestCaseCall(node))), []);
|
|---|
| 397 | };
|
|---|
| 398 |
|
|---|
| 399 | exports.getTestCallExpressionsFromDeclaredVariables = getTestCallExpressionsFromDeclaredVariables;
|
|---|
| 400 |
|
|---|
| 401 | const isTestCaseName = node => node.type === _experimentalUtils.AST_NODE_TYPES.Identifier && TestCaseName.hasOwnProperty(node.name);
|
|---|
| 402 |
|
|---|
| 403 | const isTestCaseProperty = node => isSupportedAccessor(node) && TestCaseProperty.hasOwnProperty(getAccessorValue(node));
|
|---|
| 404 | /**
|
|---|
| 405 | * Checks if the given `node` is a *call* to a test case function that would
|
|---|
| 406 | * result in tests being run by `jest`.
|
|---|
| 407 | *
|
|---|
| 408 | * Note that `.each()` does not count as a call in this context, as it will not
|
|---|
| 409 | * result in `jest` running any tests.
|
|---|
| 410 | *
|
|---|
| 411 | * @param {TSESTree.CallExpression} node
|
|---|
| 412 | *
|
|---|
| 413 | * @return {node is JestFunctionCallExpression<TestCaseName>}
|
|---|
| 414 | */
|
|---|
| 415 |
|
|---|
| 416 |
|
|---|
| 417 | const isTestCaseCall = node => {
|
|---|
| 418 | if (isTestCaseName(node.callee)) {
|
|---|
| 419 | return true;
|
|---|
| 420 | }
|
|---|
| 421 |
|
|---|
| 422 | const callee = node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee;
|
|---|
| 423 |
|
|---|
| 424 | if (callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && isTestCaseProperty(callee.property)) {
|
|---|
| 425 | // if we're an `each()`, ensure we're the outer CallExpression (i.e `.each()()`)
|
|---|
| 426 | if (getAccessorValue(callee.property) === 'each' && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.CallExpression) {
|
|---|
| 427 | return false;
|
|---|
| 428 | }
|
|---|
| 429 |
|
|---|
| 430 | return callee.object.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression ? isTestCaseName(callee.object.object) : isTestCaseName(callee.object);
|
|---|
| 431 | }
|
|---|
| 432 |
|
|---|
| 433 | return false;
|
|---|
| 434 | };
|
|---|
| 435 |
|
|---|
| 436 | exports.isTestCaseCall = isTestCaseCall;
|
|---|
| 437 |
|
|---|
| 438 | const isDescribeAlias = node => node.type === _experimentalUtils.AST_NODE_TYPES.Identifier && DescribeAlias.hasOwnProperty(node.name);
|
|---|
| 439 |
|
|---|
| 440 | const isDescribeProperty = node => isSupportedAccessor(node) && DescribeProperty.hasOwnProperty(getAccessorValue(node));
|
|---|
| 441 | /**
|
|---|
| 442 | * Checks if the given `node` is a *call* to a `describe` function that would
|
|---|
| 443 | * result in a `describe` block being created by `jest`.
|
|---|
| 444 | *
|
|---|
| 445 | * Note that `.each()` does not count as a call in this context, as it will not
|
|---|
| 446 | * result in `jest` creating any `describe` blocks.
|
|---|
| 447 | *
|
|---|
| 448 | * @param {TSESTree.CallExpression} node
|
|---|
| 449 | *
|
|---|
| 450 | * @return {node is JestFunctionCallExpression<TestCaseName>}
|
|---|
| 451 | */
|
|---|
| 452 |
|
|---|
| 453 |
|
|---|
| 454 | const isDescribeCall = node => {
|
|---|
| 455 | if (isDescribeAlias(node.callee)) {
|
|---|
| 456 | return true;
|
|---|
| 457 | }
|
|---|
| 458 |
|
|---|
| 459 | const callee = node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee;
|
|---|
| 460 |
|
|---|
| 461 | if (callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && isDescribeProperty(callee.property)) {
|
|---|
| 462 | // if we're an `each()`, ensure we're the outer CallExpression (i.e `.each()()`)
|
|---|
| 463 | if (getAccessorValue(callee.property) === 'each' && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.CallExpression) {
|
|---|
| 464 | return false;
|
|---|
| 465 | }
|
|---|
| 466 |
|
|---|
| 467 | return callee.object.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression ? isDescribeAlias(callee.object.object) : isDescribeAlias(callee.object);
|
|---|
| 468 | }
|
|---|
| 469 |
|
|---|
| 470 | return false;
|
|---|
| 471 | };
|
|---|
| 472 |
|
|---|
| 473 | exports.isDescribeCall = isDescribeCall;
|
|---|
| 474 |
|
|---|
| 475 | const collectReferences = scope => {
|
|---|
| 476 | const locals = new Set();
|
|---|
| 477 | const unresolved = new Set();
|
|---|
| 478 | let currentScope = scope;
|
|---|
| 479 |
|
|---|
| 480 | while (currentScope !== null) {
|
|---|
| 481 | for (const ref of currentScope.variables) {
|
|---|
| 482 | const isReferenceDefined = ref.defs.some(def => {
|
|---|
| 483 | return def.type !== 'ImplicitGlobalVariable';
|
|---|
| 484 | });
|
|---|
| 485 |
|
|---|
| 486 | if (isReferenceDefined) {
|
|---|
| 487 | locals.add(ref.name);
|
|---|
| 488 | }
|
|---|
| 489 | }
|
|---|
| 490 |
|
|---|
| 491 | for (const ref of currentScope.through) {
|
|---|
| 492 | unresolved.add(ref.identifier.name);
|
|---|
| 493 | }
|
|---|
| 494 |
|
|---|
| 495 | currentScope = currentScope.upper;
|
|---|
| 496 | }
|
|---|
| 497 |
|
|---|
| 498 | return {
|
|---|
| 499 | locals,
|
|---|
| 500 | unresolved
|
|---|
| 501 | };
|
|---|
| 502 | };
|
|---|
| 503 |
|
|---|
| 504 | const scopeHasLocalReference = (scope, referenceName) => {
|
|---|
| 505 | const references = collectReferences(scope);
|
|---|
| 506 | return (// referenceName was found as a local variable or function declaration.
|
|---|
| 507 | references.locals.has(referenceName) || // referenceName was not found as an unresolved reference,
|
|---|
| 508 | // meaning it is likely not an implicit global reference.
|
|---|
| 509 | !references.unresolved.has(referenceName)
|
|---|
| 510 | );
|
|---|
| 511 | };
|
|---|
| 512 |
|
|---|
| 513 | exports.scopeHasLocalReference = scopeHasLocalReference; |
|---|