Skip to content

Commit

Permalink
prefer-set-has: Use snapshot to test (#2035)
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed Feb 2, 2023
1 parent 7ed738a commit 00883a8
Show file tree
Hide file tree
Showing 7 changed files with 1,485 additions and 510 deletions.
11 changes: 3 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@
]
},
"xo": {
"extends": [
"plugin:internal-rules/all"
],
"ignores": [
".cache-eslint-remote-tester",
"eslint-remote-tester-results",
Expand Down Expand Up @@ -160,14 +163,6 @@
"eslint-plugin/require-meta-has-suggestions": "off",
"eslint-plugin/require-meta-schema": "off"
}
},
{
"files": [
"rules/**/*.js"
],
"extends": [
"plugin:internal-rules/all"
]
}
]
},
Expand Down
4 changes: 2 additions & 2 deletions rules/no-useless-spread.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,11 @@ function * unwrapSingleArraySpread(fixer, arrayExpression, sourceCode) {
] = sourceCode.getLastTokens(arrayExpression, 2);

// `[...value]`
// ^
// ^
yield fixer.remove(closingBracketToken);

// `[...value,]`
// ^
// ^
if (isCommaToken(commaToken)) {
yield fixer.remove(commaToken);
}
Expand Down
130 changes: 130 additions & 0 deletions scripts/internal-rules/fix-snapshot-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
'use strict';
const assert = require('node:assert');
const {
isCommaToken,
} = require('@eslint-community/eslint-utils');
const {methodCallSelector} = require('../../rules/selectors/index.js');

const MESSAGE_ID_DISALLOWED_PROPERTY = 'disallow-property';
const MESSAGE_ID_NO_SINGLE_CODE_OBJECT = 'use-string';
const MESSAGE_ID_REMOVE_FIX_MARK_COMMENT = 'remove-fix-mark';
const messages = {
[MESSAGE_ID_DISALLOWED_PROPERTY]: '"{{name}}" not allowed.{{autoFixEnableTip}}',
[MESSAGE_ID_NO_SINGLE_CODE_OBJECT]: 'Use string instead of object with "code".',
[MESSAGE_ID_REMOVE_FIX_MARK_COMMENT]: 'This comment should be removed.',
};

// Top-level `test.snapshot({invalid: []})`
const selector = [
'Program > ExpressionStatement.body > .expression',
// `test.snapshot()`
methodCallSelector({
argumentsLength: 1,
object: 'test',
method: 'snapshot',
}),
' > ObjectExpression.arguments:first-child',
/*
```
test.snapshot({
invalid: [], <- Property
})
```
*/
' > Property.properties',
'[computed!=true]',
'[method!=true]',
'[shorthand!=true]',
'[kind="init"]',
'[key.type="Identifier"]',
'[key.name="invalid"]',

' > ArrayExpression.value',
' > ObjectExpression.elements',
' > Property.properties[computed!=true][key.type="Identifier"]',
].join('');

function * removeObjectProperty(node, fixer, sourceCode) {
yield fixer.remove(node);
const nextToken = sourceCode.getTokenAfter(node);
if (isCommaToken(nextToken)) {
yield fixer.remove(nextToken);
}
}

// The fix deletes lots of code, disabled auto-fix by default, unless `/* fix */ test.snapshot()` pattern is used.
function hasFixMarkComment(propertyNode, sourceCode) {
const snapshotTestCall = propertyNode.parent.parent.parent.parent.parent;
assert.ok(snapshotTestCall.type === 'CallExpression');
const comment = sourceCode.getTokenBefore(snapshotTestCall, {includeComments: true});

if (
(comment?.type === 'Block' || comment?.type === 'Line')
&& comment.value.trim().toLowerCase() === 'fix'
&& (
comment.loc.start.line === snapshotTestCall.loc.start.line
|| comment.loc.start.line === snapshotTestCall.loc.start.line - 1
)
) {
return true;
}
}

module.exports = {
create(context) {
const sourceCode = context.getSourceCode();

return {
[selector](propertyNode) {
const {key} = propertyNode;

switch (key.name) {
case 'errors':
case 'output': {
const canFix = sourceCode.getCommentsInside(propertyNode).length === 0;
const hasFixMark = hasFixMarkComment(propertyNode, sourceCode);

context.report({
node: key,
messageId: MESSAGE_ID_DISALLOWED_PROPERTY,
data: {
name: key.name,
autoFixEnableTip: !hasFixMark && canFix
? ' Put /* fix */ before `test.snapshot()` to enable auto-fix.'
: '',
},
fix: hasFixMark && canFix
? fixer => removeObjectProperty(propertyNode, fixer, sourceCode)
: undefined
,
});
break;
}

case 'code': {
const testCase = propertyNode.parent;
if (testCase.properties.length === 1) {
const commentsCount = sourceCode.getCommentsInside(testCase).length
- sourceCode.getCommentsInside(propertyNode).length;
context.report({
node: testCase,
messageId: MESSAGE_ID_NO_SINGLE_CODE_OBJECT,
fix: commentsCount === 0
? fixer => fixer.replaceText(testCase, sourceCode.getText(propertyNode.value))
: undefined,
});
}

break;
}

// No default
}
},
};
},
meta: {
fixable: 'code',
messages,
},
};
37 changes: 33 additions & 4 deletions scripts/internal-rules/index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,47 @@
'use strict';

const path = require('node:path');

const pluginName = 'internal-rules';
const TEST_DIRECTORIES = [
path.join(__dirname, '../../test'),
];
const RULES_DIRECTORIES = [
path.join(__dirname, '../../rules'),
];

const rules = [
'prefer-negative-boolean-attribute',
'prefer-disallow-over-forbid',
{id: 'fix-snapshot-test', directories: TEST_DIRECTORIES},
{id: 'prefer-disallow-over-forbid', directories: RULES_DIRECTORIES},
{id: 'prefer-negative-boolean-attribute', directories: RULES_DIRECTORIES},
];

const isFileInsideDirectory = (filename, directory) => filename.startsWith(directory + path.sep);

module.exports = {
rules: Object.fromEntries(rules.map(id => [id, require(`./${id}.js`)])),
rules: Object.fromEntries(
rules.map(({id, directories}) => {
const rule = require(`./${id}.js`);
return [
id,
{
...rule,
create(context) {
const filename = context.getPhysicalFilename();
if (directories.every(directory => !isFileInsideDirectory(filename, directory))) {
return {};
}

return rule.create(context);
},
},
];
}),
),
configs: {
all: {
plugins: [pluginName],
rules: Object.fromEntries(rules.map(id => [`${pluginName}/${id}`, 'error'])),
rules: Object.fromEntries(rules.map(({id}) => [`${pluginName}/${id}`, 'error'])),
},
},
};
Loading

0 comments on commit 00883a8

Please sign in to comment.