-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: adding eslint method for deep freeze (#2904)
* feat: adding eslint method for deep freeze - iteration 1 * minor * if we already imported deepFreeze dont import again * one minor issue with array mock data * revert prefer-jasmine-matchers' * feat: adding eslint rule for using space between it blocks (#2909) * feat: adding eslint rule for using space between it blocks * minor * ignore eslint rules file from eslint lol * new method for this eslint rule * feat: adding eslint rule for converting toBe(true) to toBeTrue() etc (#2911) * feat: adding eslint rule for converting toBe(true) to toBeTrue() etc * minor * minor * feat: prefer resolveTo and rejectWith instead of returnValue(Promise.resolve()) (#2912) * feat: prefer resolveTo and rejectWith instead of returnValue(Promise.resolve()) * feat: added deepFreeze to not pollute global mock data (#2913) * feat: added deepFreeze to not pollute global mock data * feat: added deepFreeze to not pollute global mock data - Part 2 (#2914) * feat: added deepFreeze to not pollute global mock data - Part 2 * feat: added deepFreeze to not pollute global mock data - Part 3 (#2915) * feat: added deepFreeze to not pollute global mock data - Part 3 * feat: added deepFreeze to not pollute global mock data - Part 4 (#2916) * feat: added deepFreeze to not pollute global mock data - Part 4 * feat: added deepFreeze to not pollute global mock data - Part 5 (#2917) * feat: added deepFreeze to not pollute global mock data - Part 5 * feat: added deepFreeze to not pollute global mock data - Part 6 (#2918) * feat: added deepFreeze to not pollute global mock data - Part 6 * feat: added deepFreeze to not pollute global mock data - Part 7 (#2919) * feat: added deepFreeze to not pollute global mock data - Part 7 * minor * feat: added deepFreeze to not pollute global mock data - Part 8 (#2922) * feat: added deepFreeze to not pollute global mock data - Part 8 * feat: added deepFreeze to not pollute global mock data - Part 9 (#2924) * feat: added deepFreeze to not pollute global mock data - Part 9 * feat: added deepFreeze to not pollute global mock data - Part 10 (#2925) * feat: added deepFreeze to not pollute global mock data - Part 10 * feat: added deepFreeze to not pollute global mock data - Part 11 (#2927) * feat: added deepFreeze to not pollute global mock data - Part 11 * feat: added deepFreeze to not pollute global mock data - Part 12 (#2929) * feat: added deepFreeze to not pollute global mock data - Part 12 * feat: added deepFreeze to not pollute global mock data - Part 13 (#2930) * feat: added deepFreeze to not pollute global mock data - Part 13 * minor * feat: added deepFreeze to not pollute global mock data - Part 14 (#2933) * feat: added deepFreeze to not pollute global mock data - Part 14 * feat: added deepFreeze to not pollute global mock data - Part 15 (#2934) * feat: added deepFreeze to not pollute global mock data - Part 15 * test: fixing tests - part 1 (#2939) * test: fixing tests - part 1 * test: fixing tests - part 2 (#2940) * test: fixing tests - part 2 * test: fixing tests - part 3 (#2941) * test: fixing tests - part 3 * test: fixing tests - part 4 (#2943) * test: fixing tests - part 4 * test: fixing tests - part 5 (#2945) * minor
- Loading branch information
1 parent
1548728
commit 0cc9da8
Showing
268 changed files
with
4,076 additions
and
3,091 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module.exports = { | ||
rules: { | ||
'prefer-deep-freeze': require('./rules/eslint-plugin-prefer-deep-freeze'), | ||
'space-before-it-blocks': require('./rules/eslint-plugin-space-before-it-blocks'), | ||
'prefer-jasmine-matchers': require('./rules/eslint-plugin-prefer-jasmine-matchers'), | ||
'prefer-resolve-to-reject-with': require('./rules/eslint-plugin-prefer-resolve-to-reject-with') | ||
}, | ||
}; |
65 changes: 65 additions & 0 deletions
65
eslint-custom-rules/rules/eslint-plugin-prefer-deep-freeze.js
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,65 @@ | ||
module.exports = { | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
description: 'Enforce using deep-freeze-strict on all variables in *.data.ts files to avoid flaky tests', | ||
category: 'Best Practices', | ||
recommended: true, | ||
}, | ||
fixable: 'code', | ||
}, | ||
create: function(context) { | ||
return { | ||
VariableDeclarator(node) { | ||
const sourceCode = context.getSourceCode(); | ||
const filename = context.getFilename(); | ||
|
||
if (filename.endsWith('.data.ts')) { | ||
const { id, init } = node; | ||
|
||
if (id && init && (init.type === 'ObjectExpression' || init.type === 'ArrayExpression')) { | ||
const fix = (fixer) => { | ||
const variableText = sourceCode.getText(init); | ||
const start = init.range[0]; | ||
const end = init.range[1]; | ||
|
||
const fixes = []; | ||
|
||
// Check if deepFreeze is already imported | ||
const importNodes = sourceCode.ast.body.filter( | ||
(node) => node.type === 'ImportDeclaration' && node.source.value === 'deep-freeze-strict' | ||
); | ||
if (importNodes.length === 0) { | ||
// Add import statement at the top if not imported | ||
fixes.push( | ||
fixer.insertTextBeforeRange( | ||
[sourceCode.ast.range[0], sourceCode.ast.range[0]], | ||
"import deepFreeze from 'deep-freeze-strict';\n\n" | ||
) | ||
); | ||
} | ||
|
||
// Wrap the object or array with deepFreeze method | ||
fixes.push( | ||
fixer.replaceTextRange( | ||
[start, end], | ||
`deepFreeze(${variableText})` | ||
) | ||
); | ||
|
||
return fixes; | ||
}; | ||
|
||
if (init.type === 'ObjectExpression' || init.type === 'ArrayExpression') { | ||
context.report({ | ||
node, | ||
message: `Use deep-freeze-strict function on variable "${id.name}" to avoid flaky tests`, | ||
fix, | ||
}); | ||
} | ||
} | ||
} | ||
}, | ||
}; | ||
}, | ||
}; |
134 changes: 134 additions & 0 deletions
134
eslint-custom-rules/rules/eslint-plugin-prefer-jasmine-matchers.js
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,134 @@ | ||
// Use toBeNull() instead of toBe(null) or toEqual(null) | ||
// Use toBeUndefined() instead of toBe(undefined) or toEqual(undefined) | ||
// Use toBeTrue() instead of toBe(true) or toEqual(true) | ||
// Use toBeFalse() instead of toBe(false) or toEqual(false) | ||
// Use toBeNaN() instead of toBe(NaN) or toEqual(NaN) | ||
|
||
module.exports = { | ||
meta: { | ||
type: "suggestion", | ||
docs: { | ||
description: "Enforce using toBeTrue(), toBeFalse(), toBeNull(), and toBeUndefined() instead of toBe() or toEqual() with true, false, null, or undefined", | ||
category: "Best Practices", | ||
recommended: true, | ||
}, | ||
fixable: "code", | ||
schema: [], | ||
}, | ||
|
||
create: function (context) { | ||
return { | ||
CallExpression(node) { | ||
const isToBe = node.callee.property && node.callee.property.name === "toBe"; | ||
const isToEqual = node.callee.property && node.callee.property.name === "toEqual"; | ||
|
||
if ((isToBe || isToEqual) && node.arguments.length === 1) { | ||
const argValue = node.arguments[0].value; | ||
if (argValue === true) { | ||
context.report({ | ||
node, | ||
message: "Prefer using toBeTrue() instead of toBe(true) or toEqual(true)", | ||
fix: function (fixer) { | ||
const replacementText = 'toBeTrue()'; | ||
const sourceCode = context.getSourceCode(); | ||
const nodeText = sourceCode.getText(node); | ||
|
||
// Find the correct part of the code to replace | ||
const match = nodeText.match(/\.toBe\((true)\)|\.toEqual\((true)\)/); | ||
if (match) { | ||
const start = node.callee.property.range[0]; | ||
const end = node.callee.property.range[1] + 6; // Adjust end position | ||
return fixer.replaceTextRange([start, end], replacementText); | ||
} | ||
|
||
return null; // No match found, no fix needed | ||
} | ||
}); | ||
} else if (argValue === false) { | ||
context.report({ | ||
node, | ||
message: "Prefer using toBeFalse() instead of toBe(false) or toEqual(false)", | ||
fix: function (fixer) { | ||
const replacementText = 'toBeFalse()'; | ||
const sourceCode = context.getSourceCode(); | ||
const nodeText = sourceCode.getText(node); | ||
|
||
// Find the correct part of the code to replace | ||
const match = nodeText.match(/\.toBe\((false)\)|\.toEqual\((false)\)/); | ||
if (match) { | ||
const start = node.callee.property.range[0]; | ||
const end = node.callee.property.range[1] + 7; // Adjust end position | ||
return fixer.replaceTextRange([start, end], replacementText); | ||
} | ||
|
||
return null; // No match found, no fix needed | ||
} | ||
}); | ||
} else if (argValue === null) { | ||
context.report({ | ||
node, | ||
message: "Prefer using toBeNull() instead of toBe(null) or toEqual(null)", | ||
fix: function (fixer) { | ||
const replacementText = 'toBeNull()'; | ||
const sourceCode = context.getSourceCode(); | ||
const nodeText = sourceCode.getText(node); | ||
|
||
// Find the correct part of the code to replace | ||
const match = nodeText.match(/\.toBe\((null)\)|\.toEqual\((null)\)/); | ||
if (match) { | ||
const start = node.callee.property.range[0]; | ||
const end = node.callee.property.range[1] + 6; // Adjust end position | ||
return fixer.replaceTextRange([start, end], replacementText); | ||
} | ||
|
||
return null; // No match found, no fix needed | ||
} | ||
}); | ||
} else if (argValue === undefined && node.arguments[0].name === 'undefined') { | ||
// Comparing the name of the argument to undefined since every variable is undefined by default in AST | ||
context.report({ | ||
node, | ||
message: "Prefer using toBeUndefined() instead of toBe(undefined) or toEqual(undefined)", | ||
fix: function (fixer) { | ||
const replacementText = 'toBeUndefined()'; | ||
const sourceCode = context.getSourceCode(); | ||
const nodeText = sourceCode.getText(node); | ||
|
||
// Find the correct part of the code to replace | ||
const match = nodeText.match(/\.toBe\((undefined)\)|\.toEqual\((undefined)\)/); | ||
if (match) { | ||
const start = node.callee.property.range[0]; | ||
const end = node.callee.property.range[1] + 11; // Adjust end position | ||
return fixer.replaceTextRange([start, end], replacementText); | ||
} | ||
|
||
return null; // No match found, no fix needed | ||
} | ||
}); | ||
} else if (node.arguments[0].name === 'NaN') { | ||
// Since NaN === NaN returns false, we are comparing the name of the argument to NaN | ||
context.report({ | ||
node, | ||
message: "Prefer using toBeNaN() instead of toBe(NaN) or toEqual(NaN)", | ||
fix: function (fixer) { | ||
const replacementText = 'toBeNaN()'; | ||
const sourceCode = context.getSourceCode(); | ||
const nodeText = sourceCode.getText(node); | ||
|
||
// Find the correct part of the code to replace | ||
const match = nodeText.match(/\.toBe\((NaN)\)|\.toEqual\((NaN)\)/); | ||
if (match) { | ||
const start = node.callee.property.range[0]; | ||
const end = node.callee.property.range[1] + 5; // Adjust end position | ||
return fixer.replaceTextRange([start, end], replacementText); | ||
} | ||
|
||
return null; // No match found, no fix needed | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
}; |
52 changes: 52 additions & 0 deletions
52
eslint-custom-rules/rules/eslint-plugin-prefer-resolve-to-reject-with.js
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,52 @@ | ||
const SpyStrategyCall = `CallExpression:matches( | ||
[callee.object.property.name=and], | ||
[callee.object.callee.property.name=withArgs] | ||
)`.replace(/\s+/g, ' '); | ||
|
||
const ReturnStrategy = `${SpyStrategyCall}[callee.property.name=returnValue]`; | ||
|
||
// Matches Promise.{resolve,reject}(X) | ||
const PromiseCall = 'CallExpression[callee.object.name=Promise]'; | ||
const SettledPromise = `${PromiseCall}[callee.property.name=/resolve|reject/]`; | ||
|
||
module.exports = { | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
description: "Enforce using resolveTo() and rejectWith() instead of Promise.resolve() and Promise.reject() in Jasmine tests", | ||
category: "Best Practices", | ||
recommended: true, | ||
}, | ||
fixable: 'code', | ||
schema: [] | ||
}, | ||
|
||
create: context => ({ | ||
[`${ReturnStrategy} > ${SettledPromise}.arguments:first-child`] (promiseCall) { | ||
const returnStrategyCall = promiseCall.parent; | ||
const returnValueMethod = returnStrategyCall.callee.property; | ||
const preferredMethod = promiseCall.callee.property.name === 'resolve' | ||
? 'resolveTo' : 'rejectWith'; | ||
|
||
context.report({ | ||
message: `Prefer ${preferredMethod}`, | ||
loc: { | ||
start: returnValueMethod.loc.start, | ||
end: returnStrategyCall.loc.end | ||
}, | ||
fix (fixer) { | ||
const code = context.getSourceCode(); | ||
return [ | ||
// Replace Promise constructor call with its arguments | ||
fixer.remove(promiseCall.callee), | ||
fixer.remove(code.getTokenAfter(promiseCall.callee)), | ||
fixer.remove(code.getLastToken(promiseCall)), | ||
|
||
// Replace returnValue method with resolveTo or rejectWith | ||
fixer.replaceText(returnValueMethod, preferredMethod) | ||
]; | ||
} | ||
}) | ||
} | ||
}) | ||
}; |
53 changes: 53 additions & 0 deletions
53
eslint-custom-rules/rules/eslint-plugin-space-before-it-blocks.js
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,53 @@ | ||
var suiteRegexp = /^(f|x)?describe$/; | ||
|
||
module.exports = { | ||
meta: { | ||
type: 'layout', | ||
docs: { | ||
description: 'Enforce a space before each `it` block in spec.ts files', | ||
category: 'Stylistic Issues', | ||
recommended: true, | ||
}, | ||
fixable: 'code', | ||
}, | ||
create: function (context) { | ||
return { | ||
CallExpression: function (node) { | ||
var declWithoutPadding = null; | ||
if (suiteRegexp.test(node.callee.name)) { | ||
var declarations = getDescribeDeclarationsContent(node); | ||
declarations.forEach((decl, i) => { | ||
var next = declarations[i + 1]; | ||
if (next && !(next.loc.start.line - decl.loc.end.line >= 2)) { | ||
declWithoutPadding = decl; | ||
} | ||
}) | ||
} | ||
if (declWithoutPadding) { | ||
context.report({ | ||
message: 'Use new line between declarations for more readability', | ||
node, | ||
loc: node.loc, | ||
fix (fixer) { | ||
return fixer.insertTextAfter(declWithoutPadding, '\n'); | ||
}, | ||
}) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
function getDescribeDeclarationsContent (describe) { | ||
var declartionsRegexp = /^(((before|after)(Each|All))|^(f|x)?(it|describe))$/; | ||
var declarations = []; | ||
if (describe.arguments && describe.arguments[1] && describe.arguments[1].body && describe.arguments[1].body.body) { | ||
var content = describe.arguments[1].body.body; | ||
content.forEach(node => { | ||
if (node.type === 'ExpressionStatement' && node.expression.callee && declartionsRegexp.test(node.expression.callee.name)) { | ||
declarations.push(node); | ||
} | ||
}) | ||
} | ||
return declarations; | ||
} |
Oops, something went wrong.