-
-
Notifications
You must be signed in to change notification settings - Fork 367
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
prefer-set-has
: Use snapshot to test (#2035)
- Loading branch information
Showing
7 changed files
with
1,485 additions
and
510 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'])), | ||
}, | ||
}, | ||
}; |
Oops, something went wrong.