Skip to content

Move rules settings to ESLint shared config: part 1 - utils detection #237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Oct 20, 2020
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
16 changes: 4 additions & 12 deletions lib/rules/no-node-access.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils';
import { getDocsUrl, ALL_RETURNING_NODES } from '../utils';
import { isIdentifier } from '../node-utils';
import detect from '../testing-library-detection';

export const RULE_NAME = 'no-node-access';
export type MessageIds = 'noNodeAccess';
Expand All @@ -24,19 +25,11 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
},
defaultOptions: [],

create(context) {
let isImportingTestingLibrary = false;

function checkTestingEnvironment(node: TSESTree.ImportDeclaration) {
isImportingTestingLibrary = /testing-library/g.test(
node.source.value as string
);
}

create: detect((context: any, _: any, helpers: any) => {
function showErrorForNodeAccess(node: TSESTree.MemberExpression) {
isIdentifier(node.property) &&
ALL_RETURNING_NODES.includes(node.property.name) &&
isImportingTestingLibrary &&
helpers.getIsImportingTestingLibrary() &&
context.report({
node: node,
loc: node.property.loc.start,
Expand All @@ -45,9 +38,8 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
}

return {
['ImportDeclaration']: checkTestingEnvironment,
['ExpressionStatement MemberExpression']: showErrorForNodeAccess,
['VariableDeclarator MemberExpression']: showErrorForNodeAccess,
};
},
}),
});
52 changes: 52 additions & 0 deletions lib/testing-library-detection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { TSESTree } from '@typescript-eslint/experimental-utils';

/**
* Enhances a given rule with helpers to detect Testing Library utils.
*/
function testingLibraryDetection(rule: any) {
return (context: any, options: any) => {
let isImportingTestingLibrary = false;

// TODO: init here options based on shared ESLint config

// helpers for Testing Library detection
const helpers = {
getIsImportingTestingLibrary() {
return isImportingTestingLibrary;
},
};

// instructions for Testing Library detection
const detectionInstructions = {
ImportDeclaration(node: TSESTree.ImportDeclaration) {
isImportingTestingLibrary = /testing-library/g.test(
node.source.value as string
);
},
};

// update given rule to inject Testing Library detection
const ruleInstructions = rule(context, options, helpers);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would options be where the eslint configuration (for instance: the custom import) is?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, these options are always the rule options. If you want to access shared ones, you'll need to get them through context. The idea is not having to check shared options manually within rules, as the TL detection will check which are those options to decide if a method is from Testing Library or not.

const enhancedRuleInstructions = Object.assign({}, ruleInstructions);

Object.keys(detectionInstructions).forEach((instruction) => {
enhancedRuleInstructions[instruction] = (node: any) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized I lost some types I added correctly like TSESTree.Node here. I was so frustrated I reverted everything 😂

if (instruction in detectionInstructions) {
detectionInstructions[instruction](node);
}

if (ruleInstructions[instruction]) {
return ruleInstructions[instruction](node);
}
};
});

return enhancedRuleInstructions;
};
}

function detect(rule: any) {
return testingLibraryDetection(rule);
}

export default detect;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit lost, but given default exports just take the name you want, we could just export testingLibraryDetection

if you wanted to enforce the name detect, then detect should be a named export. Unless to expect to add more code here 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a stupid function not necessary anymore. I first implemented this by calling testingLibraryDetection from this detect one using bind to pass the rule. Then I realized I could do the same just with a high-order function, but I forgot about removing this one! I'll get rid of it.