|
| 1 | +'use strict'; |
| 2 | +const assert = require('node:assert'); |
| 3 | +const { |
| 4 | + isCommaToken, |
| 5 | +} = require('@eslint-community/eslint-utils'); |
| 6 | +const {methodCallSelector} = require('../../rules/selectors/index.js'); |
| 7 | + |
| 8 | +const MESSAGE_ID_DISALLOWED_PROPERTY = 'disallow-property'; |
| 9 | +const MESSAGE_ID_NO_SINGLE_CODE_OBJECT = 'use-string'; |
| 10 | +const MESSAGE_ID_REMOVE_FIX_MARK_COMMENT = 'remove-fix-mark'; |
| 11 | +const messages = { |
| 12 | + [MESSAGE_ID_DISALLOWED_PROPERTY]: '"{{name}}" not allowed.{{autoFixEnableTip}}', |
| 13 | + [MESSAGE_ID_NO_SINGLE_CODE_OBJECT]: 'Use string instead of object with "code".', |
| 14 | + [MESSAGE_ID_REMOVE_FIX_MARK_COMMENT]: 'This comment should be removed.', |
| 15 | +}; |
| 16 | + |
| 17 | +// Top-level `test.snapshot({invalid: []})` |
| 18 | +const selector = [ |
| 19 | + 'Program > ExpressionStatement.body > .expression', |
| 20 | + // `test.snapshot()` |
| 21 | + methodCallSelector({ |
| 22 | + argumentsLength: 1, |
| 23 | + object: 'test', |
| 24 | + method: 'snapshot', |
| 25 | + }), |
| 26 | + ' > ObjectExpression.arguments:first-child', |
| 27 | + /* |
| 28 | + ``` |
| 29 | + test.snapshot({ |
| 30 | + invalid: [], <- Property |
| 31 | + }) |
| 32 | + ``` |
| 33 | + */ |
| 34 | + ' > Property.properties', |
| 35 | + '[computed!=true]', |
| 36 | + '[method!=true]', |
| 37 | + '[shorthand!=true]', |
| 38 | + '[kind="init"]', |
| 39 | + '[key.type="Identifier"]', |
| 40 | + '[key.name="invalid"]', |
| 41 | + |
| 42 | + ' > ArrayExpression.value', |
| 43 | + ' > ObjectExpression.elements', |
| 44 | + ' > Property.properties[computed!=true][key.type="Identifier"]', |
| 45 | +].join(''); |
| 46 | + |
| 47 | +function * removeObjectProperty(node, fixer, sourceCode) { |
| 48 | + yield fixer.remove(node); |
| 49 | + const nextToken = sourceCode.getTokenAfter(node); |
| 50 | + if (isCommaToken(nextToken)) { |
| 51 | + yield fixer.remove(nextToken); |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +// The fix deletes lots of code, disabled auto-fix by default, unless `/* fix */ test.snapshot()` pattern is used. |
| 56 | +function hasFixMarkComment(propertyNode, sourceCode) { |
| 57 | + const snapshotTestCall = propertyNode.parent.parent.parent.parent.parent; |
| 58 | + assert.ok(snapshotTestCall.type === 'CallExpression'); |
| 59 | + const comment = sourceCode.getTokenBefore(snapshotTestCall, {includeComments: true}); |
| 60 | + |
| 61 | + if ( |
| 62 | + (comment?.type === 'Block' || comment?.type === 'Line') |
| 63 | + && comment.value.trim().toLowerCase() === 'fix' |
| 64 | + && ( |
| 65 | + comment.loc.start.line === snapshotTestCall.loc.start.line |
| 66 | + || comment.loc.start.line === snapshotTestCall.loc.start.line - 1 |
| 67 | + ) |
| 68 | + ) { |
| 69 | + return true; |
| 70 | + } |
| 71 | +} |
| 72 | + |
| 73 | +module.exports = { |
| 74 | + create(context) { |
| 75 | + const sourceCode = context.getSourceCode(); |
| 76 | + |
| 77 | + return { |
| 78 | + [selector](propertyNode) { |
| 79 | + const {key} = propertyNode; |
| 80 | + |
| 81 | + switch (key.name) { |
| 82 | + case 'errors': |
| 83 | + case 'output': { |
| 84 | + const canFix = sourceCode.getCommentsInside(propertyNode).length === 0; |
| 85 | + const hasFixMark = hasFixMarkComment(propertyNode, sourceCode); |
| 86 | + |
| 87 | + context.report({ |
| 88 | + node: key, |
| 89 | + messageId: MESSAGE_ID_DISALLOWED_PROPERTY, |
| 90 | + data: { |
| 91 | + name: key.name, |
| 92 | + autoFixEnableTip: !hasFixMark && canFix |
| 93 | + ? ' Put /* fix */ before `test.snapshot()` to enable auto-fix.' |
| 94 | + : '', |
| 95 | + }, |
| 96 | + fix: hasFixMark && canFix |
| 97 | + ? fixer => removeObjectProperty(propertyNode, fixer, sourceCode) |
| 98 | + : undefined |
| 99 | + , |
| 100 | + }); |
| 101 | + break; |
| 102 | + } |
| 103 | + |
| 104 | + case 'code': { |
| 105 | + const testCase = propertyNode.parent; |
| 106 | + if (testCase.properties.length === 1) { |
| 107 | + const commentsCount = sourceCode.getCommentsInside(testCase).length |
| 108 | + - sourceCode.getCommentsInside(propertyNode).length; |
| 109 | + context.report({ |
| 110 | + node: testCase, |
| 111 | + messageId: MESSAGE_ID_NO_SINGLE_CODE_OBJECT, |
| 112 | + fix: commentsCount === 0 |
| 113 | + ? fixer => fixer.replaceText(testCase, sourceCode.getText(propertyNode.value)) |
| 114 | + : undefined, |
| 115 | + }); |
| 116 | + } |
| 117 | + |
| 118 | + break; |
| 119 | + } |
| 120 | + |
| 121 | + // No default |
| 122 | + } |
| 123 | + }, |
| 124 | + }; |
| 125 | + }, |
| 126 | + meta: { |
| 127 | + fixable: 'code', |
| 128 | + messages, |
| 129 | + }, |
| 130 | +}; |
0 commit comments