Skip to content

Commit 3d023c7

Browse files
committed
refactor: move utility functions closer to where they are used
1 parent b52c802 commit 3d023c7

File tree

6 files changed

+175
-15
lines changed

6 files changed

+175
-15
lines changed

.pkgs/eslint-plugin-local/dist/index.js

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as AST from '@eslint-react/ast';
22
import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
3-
import * as VAR from '@eslint-react/var';
3+
import { _ } from '@eslint-react/eff';
4+
import { findVariable } from '@eslint-react/var';
5+
import { AST_NODE_TYPES as AST_NODE_TYPES$1 } from '@typescript-eslint/types';
46
import { nullThrows, NullThrowsReasons } from '@typescript-eslint/utils/eslint-utils';
57

68
// package.json
@@ -10,6 +12,40 @@ function getDocsUrl() {
1012
return "TODO: add docs for local ESLint rules";
1113
}
1214
var createRule = ESLintUtils.RuleCreator(getDocsUrl);
15+
function isInitializedFromSource(name2, source, initialScope) {
16+
const latestDef = findVariable(name2, initialScope)?.defs.at(-1);
17+
if (latestDef == null) return false;
18+
const { node, parent } = latestDef;
19+
if (node.type === AST_NODE_TYPES$1.VariableDeclarator && node.init != null) {
20+
const { init } = node;
21+
if (init.type === AST_NODE_TYPES$1.MemberExpression && init.object.type === AST_NODE_TYPES$1.Identifier) {
22+
return isInitializedFromSource(init.object.name, source, initialScope);
23+
}
24+
if (init.type === AST_NODE_TYPES$1.Identifier) {
25+
return isInitializedFromSource(init.name, source, initialScope);
26+
}
27+
const args = getRequireExpressionArguments(init);
28+
const arg0 = args?.[0];
29+
if (arg0 == null || !AST.isStringLiteral(arg0)) {
30+
return false;
31+
}
32+
return arg0.value === source || arg0.value.startsWith(`${source}/`);
33+
}
34+
return parent?.type === AST_NODE_TYPES$1.ImportDeclaration && parent.source.value === source;
35+
}
36+
function getRequireExpressionArguments(node) {
37+
switch (true) {
38+
// require('source')
39+
case (node.type === AST_NODE_TYPES$1.CallExpression && node.callee.type === AST_NODE_TYPES$1.Identifier && node.callee.name === "require"): {
40+
return node.arguments;
41+
}
42+
// require('source').variable
43+
case node.type === AST_NODE_TYPES$1.MemberExpression: {
44+
return getRequireExpressionArguments(node.object);
45+
}
46+
}
47+
return _;
48+
}
1349

1450
// src/rules/avoid-multiline-template-expression.ts
1551
var RULE_NAME = "avoid-multiline-template-expression";
@@ -43,6 +79,8 @@ var avoid_multiline_template_expression_default = createRule({
4379
},
4480
defaultOptions: []
4581
});
82+
83+
// src/rules/no-shadow-underscore.ts
4684
var RULE_NAME2 = "no-shadow-underscore";
4785
var RULE_FEATURES2 = [
4886
"CHK"
@@ -64,7 +102,7 @@ var no_shadow_underscore_default = createRule({
64102
return {
65103
"Identifier[name='_']"(node) {
66104
const initialScope = context.sourceCode.getScope(node);
67-
const isFromImport = VAR.isInitializedFromSource("_", "@eslint-react/eff", initialScope);
105+
const isFromImport = isInitializedFromSource("_", "@eslint-react/eff", initialScope);
68106
if (!isFromImport) {
69107
context.report({
70108
messageId: "noShadowUnderscore",

.pkgs/eslint-plugin-local/src/rules/no-shadow-underscore.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import type { RuleFeature } from "@eslint-react/shared";
2-
import * as VAR from "@eslint-react/var";
32
import type { TSESTree } from "@typescript-eslint/types";
43
import type { CamelCase } from "string-ts";
54

6-
import { createRule } from "../utils";
5+
import { createRule, isInitializedFromSource } from "../utils";
76

87
export const RULE_NAME = "no-shadow-underscore";
98

@@ -30,7 +29,7 @@ export default createRule<[], MessageID>({
3029
return {
3130
"Identifier[name='_']"(node: TSESTree.Identifier & { name: "_" }) {
3231
const initialScope = context.sourceCode.getScope(node);
33-
const isFromImport = VAR.isInitializedFromSource("_", "@eslint-react/eff", initialScope);
32+
const isFromImport = isInitializedFromSource("_", "@eslint-react/eff", initialScope);
3433
if (!isFromImport) {
3534
context.report({
3635
messageId: "noShadowUnderscore",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from "./create-rule";
2+
export * from "./is-initialized-from-source";
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import * as AST from "@eslint-react/ast";
2+
import { _ } from "@eslint-react/eff";
3+
import { findVariable } from "@eslint-react/var";
4+
import type { Scope } from "@typescript-eslint/scope-manager";
5+
import type { TSESTree } from "@typescript-eslint/types";
6+
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
7+
8+
/**
9+
* Check if an identifier is initialized from the given source
10+
* @param name The top-level identifier's name
11+
* @param source The import source to check against
12+
* @param initialScope Initial scope to search for the identifier
13+
* @returns Whether the identifier is initialized from the given source
14+
*/
15+
export function isInitializedFromSource(
16+
name: string,
17+
source: string,
18+
initialScope: Scope,
19+
): boolean {
20+
const latestDef = findVariable(name, initialScope)?.defs.at(-1);
21+
if (latestDef == null) return false;
22+
const { node, parent } = latestDef;
23+
if (node.type === T.VariableDeclarator && node.init != null) {
24+
const { init } = node;
25+
// check for: `variable = Source.variable`
26+
if (init.type === T.MemberExpression && init.object.type === T.Identifier) {
27+
return isInitializedFromSource(init.object.name, source, initialScope);
28+
}
29+
// check for: `{ variable } = Source`
30+
if (init.type === T.Identifier) {
31+
return isInitializedFromSource(init.name, source, initialScope);
32+
}
33+
// check for: `variable = require('source')` or `variable = require('source').variable`
34+
const args = getRequireExpressionArguments(init);
35+
const arg0 = args?.[0];
36+
if (arg0 == null || !AST.isStringLiteral(arg0)) {
37+
return false;
38+
}
39+
// check for: `require('source')` or `require('source/...')`
40+
return arg0.value === source
41+
|| arg0
42+
.value
43+
.startsWith(`${source}/`);
44+
}
45+
// latest definition is an import declaration: import { variable } from 'source'
46+
return parent?.type === T.ImportDeclaration && parent.source.value === source;
47+
}
48+
49+
function getRequireExpressionArguments(node: TSESTree.Node): TSESTree.CallExpressionArgument[] | _ {
50+
switch (true) {
51+
// require('source')
52+
case node.type === T.CallExpression
53+
&& node.callee.type === T.Identifier
54+
&& node.callee.name === "require": {
55+
return node.arguments;
56+
}
57+
// require('source').variable
58+
case node.type === T.MemberExpression: {
59+
return getRequireExpressionArguments(node.object);
60+
}
61+
}
62+
return _;
63+
}

packages/core/docs/functions/isInitializedFromReact.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,32 @@
66

77
# Function: isInitializedFromReact()
88

9-
> **isInitializedFromReact**(`name`, `importSource`, `initialScope`): `boolean`
9+
> **isInitializedFromReact**(`name`, `source`, `initialScope`): `boolean`
10+
11+
Check if an identifier is initialized from react
1012

1113
## Parameters
1214

1315
### name
1416

1517
`string`
1618

17-
### importSource
19+
The top-level identifier's name
20+
21+
### source
1822

1923
`string`
2024

25+
The import source to check against
26+
2127
### initialScope
2228

2329
[`Scope`](../-internal-/type-aliases/Scope.md)
2430

31+
Initial scope to search for the identifier
32+
2533
## Returns
2634

2735
`boolean`
36+
37+
Whether the identifier is initialized from react
Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,64 @@
1+
import * as AST from "@eslint-react/ast";
2+
import { _ } from "@eslint-react/eff";
13
import * as VAR from "@eslint-react/var";
24
import type { Scope } from "@typescript-eslint/scope-manager";
5+
import type { TSESTree } from "@typescript-eslint/types";
6+
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
37

8+
/**
9+
* Check if an identifier is initialized from react
10+
* @param name The top-level identifier's name
11+
* @param source The import source to check against
12+
* @param initialScope Initial scope to search for the identifier
13+
* @returns Whether the identifier is initialized from react
14+
*/
415
export function isInitializedFromReact(
516
name: string,
6-
importSource: string,
17+
source: string,
718
initialScope: Scope,
8-
) {
9-
return name.toLowerCase() === "react"
10-
|| VAR.isInitializedFromSource(
11-
name,
12-
importSource,
13-
initialScope,
14-
);
19+
): boolean {
20+
if (name.toLowerCase() === "react") return true;
21+
const latestDef = VAR.findVariable(name, initialScope)?.defs.at(-1);
22+
if (latestDef == null) return false;
23+
const { node, parent } = latestDef;
24+
if (node.type === T.VariableDeclarator && node.init != null) {
25+
const { init } = node;
26+
// check for: `variable = Source.variable`
27+
if (init.type === T.MemberExpression && init.object.type === T.Identifier) {
28+
return isInitializedFromReact(init.object.name, source, initialScope);
29+
}
30+
// check for: `{ variable } = Source`
31+
if (init.type === T.Identifier) {
32+
return isInitializedFromReact(init.name, source, initialScope);
33+
}
34+
// check for: `variable = require('source')` or `variable = require('source').variable`
35+
const args = getRequireExpressionArguments(init);
36+
const arg0 = args?.[0];
37+
if (arg0 == null || !AST.isStringLiteral(arg0)) {
38+
return false;
39+
}
40+
// check for: `require('source')` or `require('source/...')`
41+
return arg0.value === source
42+
|| arg0
43+
.value
44+
.startsWith(`${source}/`);
45+
}
46+
// latest definition is an import declaration: import { variable } from 'source'
47+
return parent?.type === T.ImportDeclaration && parent.source.value === source;
48+
}
49+
50+
function getRequireExpressionArguments(node: TSESTree.Node): TSESTree.CallExpressionArgument[] | _ {
51+
switch (true) {
52+
// require('source')
53+
case node.type === T.CallExpression
54+
&& node.callee.type === T.Identifier
55+
&& node.callee.name === "require": {
56+
return node.arguments;
57+
}
58+
// require('source').variable
59+
case node.type === T.MemberExpression: {
60+
return getRequireExpressionArguments(node.object);
61+
}
62+
}
63+
return _;
1564
}

0 commit comments

Comments
 (0)