Skip to content
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

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

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
37 changes: 37 additions & 0 deletions lib/create-testing-library-rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ESLintUtils, TSESLint } from '@typescript-eslint/experimental-utils';
import { getDocsUrl } from './utils';
import {
detectTestingLibraryUtils,
DetectionHelpers,
} from './detect-testing-library-utils';

type CreateRuleMetaDocs = Omit<TSESLint.RuleMetaDataDocs, 'url'>;
type CreateRuleMeta<TMessageIds extends string> = {
docs: CreateRuleMetaDocs;
} & Omit<TSESLint.RuleMetaData<TMessageIds>, 'docs'>;
Belco90 marked this conversation as resolved.
Show resolved Hide resolved

export function createTestingLibraryRule<
TOptions extends readonly unknown[],
TMessageIds extends string,
TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener
>(
config: Readonly<{
name: string;
meta: CreateRuleMeta<TMessageIds>;
defaultOptions: Readonly<TOptions>;
create: (
context: Readonly<TSESLint.RuleContext<TMessageIds, TOptions>>,
optionsWithDefault: Readonly<TOptions>,
detectionHelpers: Readonly<DetectionHelpers>
) => TRuleListener;
}>
) {
const { create, ...remainingConfig } = config;

return ESLintUtils.RuleCreator(getDocsUrl)({
...remainingConfig,
create: detectTestingLibraryUtils<TOptions, TMessageIds, TRuleListener>(
create
),
Comment on lines +34 to +36
Copy link
Member Author

Choose a reason for hiding this comment

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

Where all the magic happens now. It looks so simple after all the time I spent on it that I feel kinda stupid.

Copy link
Member

Choose a reason for hiding this comment

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

Nice!
It actually looks easier and cleaner than expected ... that's easy to say when it's written.

Copy link
Collaborator

Choose a reason for hiding this comment

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

this looks great! Great job!

});
}
65 changes: 65 additions & 0 deletions lib/detect-testing-library-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';

export type DetectionHelpers = {
getIsImportingTestingLibrary: () => boolean;
};

/**
* Enhances a given rule `create` with helpers to detect Testing Library utils.
*/
export function detectTestingLibraryUtils<
TOptions extends readonly unknown[],
TMessageIds extends string,
TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener
>(
ruleCreate: (
context: Readonly<TSESLint.RuleContext<TMessageIds, TOptions>>,
optionsWithDefault: Readonly<TOptions>,
detectionHelpers: Readonly<DetectionHelpers>
) => TRuleListener
) {
return (
context: Readonly<TSESLint.RuleContext<TMessageIds, TOptions>>,
optionsWithDefault: Readonly<TOptions>
): TRuleListener => {
let isImportingTestingLibrary = false;

// TODO: init here options based on shared ESLint config
timdeschryver marked this conversation as resolved.
Show resolved Hide resolved

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

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

// update given rule to inject Testing Library detection
const ruleInstructions = ruleCreate(context, optionsWithDefault, helpers);
const enhancedRuleInstructions = Object.assign({}, ruleInstructions);

Object.keys(detectionInstructions).forEach((instruction) => {
(enhancedRuleInstructions as TSESLint.RuleListener)[instruction] = (
node
) => {
if (instruction in detectionInstructions) {
detectionInstructions[instruction](node);
}

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

return enhancedRuleInstructions;
};
}
20 changes: 6 additions & 14 deletions lib/rules/no-node-access.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils';
import { getDocsUrl, ALL_RETURNING_NODES } from '../utils';
import { TSESTree } from '@typescript-eslint/experimental-utils';
import { ALL_RETURNING_NODES } from '../utils';
import { isIdentifier } from '../node-utils';
import { createTestingLibraryRule } from '../create-testing-library-rule';

export const RULE_NAME = 'no-node-access';
export type MessageIds = 'noNodeAccess';
type Options = [];

export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
export default createTestingLibraryRule<Options, MessageIds>({
Copy link
Member Author

Choose a reason for hiding this comment

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

Just creating rules like this everything is hooked and helpers are passed!

name: RULE_NAME,
meta: {
type: 'problem',
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: (context, _, helpers) => {
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 gets automatic types as I wanted originally 💯

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,7 +38,6 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
}

return {
['ImportDeclaration']: checkTestingEnvironment,
['ExpressionStatement MemberExpression']: showErrorForNodeAccess,
['VariableDeclarator MemberExpression']: showErrorForNodeAccess,
};
Expand Down