@openapi-qraft/eslint-plugin-query 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +131 -0
  3. package/dist/cjs/index.cjs +44 -0
  4. package/dist/cjs/index.cjs.map +1 -0
  5. package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.rule.cjs +116 -0
  6. package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.rule.cjs.map +1 -0
  7. package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.utils.cjs +15 -0
  8. package/dist/cjs/rules/no-rest-destructuring/no-rest-destructuring.utils.cjs.map +1 -0
  9. package/dist/cjs/rules/no-unstable-deps/no-unstable-deps.rule.cjs +126 -0
  10. package/dist/cjs/rules/no-unstable-deps/no-unstable-deps.rule.cjs.map +1 -0
  11. package/dist/cjs/rules.cjs +12 -0
  12. package/dist/cjs/rules.cjs.map +1 -0
  13. package/dist/cjs/utils/create-client-name-regex.cjs +27 -0
  14. package/dist/cjs/utils/create-client-name-regex.cjs.map +1 -0
  15. package/dist/cjs/utils/get-docs-url.cjs +6 -0
  16. package/dist/cjs/utils/get-docs-url.cjs.map +1 -0
  17. package/dist/cjs/utils/get-root-identifier.cjs +22 -0
  18. package/dist/cjs/utils/get-root-identifier.cjs.map +1 -0
  19. package/dist/esm/index.js +37 -0
  20. package/dist/esm/index.js.map +1 -0
  21. package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.rule.js +113 -0
  22. package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.rule.js.map +1 -0
  23. package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.utils.js +13 -0
  24. package/dist/esm/rules/no-rest-destructuring/no-rest-destructuring.utils.js.map +1 -0
  25. package/dist/esm/rules/no-unstable-deps/no-unstable-deps.rule.js +121 -0
  26. package/dist/esm/rules/no-unstable-deps/no-unstable-deps.rule.js.map +1 -0
  27. package/dist/esm/rules.js +10 -0
  28. package/dist/esm/rules.js.map +1 -0
  29. package/dist/esm/utils/create-client-name-regex.js +25 -0
  30. package/dist/esm/utils/create-client-name-regex.js.map +1 -0
  31. package/dist/esm/utils/get-docs-url.js +4 -0
  32. package/dist/esm/utils/get-docs-url.js.map +1 -0
  33. package/dist/esm/utils/get-root-identifier.js +20 -0
  34. package/dist/esm/utils/get-root-identifier.js.map +1 -0
  35. package/dist/types/index.d.ts +23 -0
  36. package/dist/types/index.d.ts.map +1 -0
  37. package/dist/types/rules/no-rest-destructuring/no-rest-destructuring.rule.d.ts +5 -0
  38. package/dist/types/rules/no-rest-destructuring/no-rest-destructuring.rule.d.ts.map +1 -0
  39. package/dist/types/rules/no-rest-destructuring/no-rest-destructuring.utils.d.ts +5 -0
  40. package/dist/types/rules/no-rest-destructuring/no-rest-destructuring.utils.d.ts.map +1 -0
  41. package/dist/types/rules/no-unstable-deps/no-unstable-deps.rule.d.ts +7 -0
  42. package/dist/types/rules/no-unstable-deps/no-unstable-deps.rule.d.ts.map +1 -0
  43. package/dist/types/rules.d.ts +4 -0
  44. package/dist/types/rules.d.ts.map +1 -0
  45. package/dist/types/types.d.ts +4 -0
  46. package/dist/types/types.d.ts.map +1 -0
  47. package/dist/types/utils/create-client-name-regex.d.ts +2 -0
  48. package/dist/types/utils/create-client-name-regex.d.ts.map +1 -0
  49. package/dist/types/utils/get-docs-url.d.ts +2 -0
  50. package/dist/types/utils/get-docs-url.d.ts.map +1 -0
  51. package/dist/types/utils/get-root-identifier.d.ts +3 -0
  52. package/dist/types/utils/get-root-identifier.d.ts.map +1 -0
  53. package/package.json +67 -0
  54. package/src/index.ts +54 -0
  55. package/src/rules/no-rest-destructuring/no-rest-destructuring.rule.ts +135 -0
  56. package/src/rules/no-rest-destructuring/no-rest-destructuring.utils.ts +11 -0
  57. package/src/rules/no-unstable-deps/no-unstable-deps.rule.ts +137 -0
  58. package/src/rules.ts +17 -0
  59. package/src/types.ts +3 -0
  60. package/src/utils/create-client-name-regex.ts +32 -0
  61. package/src/utils/get-docs-url.ts +2 -0
  62. package/src/utils/get-root-identifier.ts +25 -0
@@ -0,0 +1,113 @@
1
+ import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { createClientNameRegex } from '../../utils/create-client-name-regex.js';
3
+ import { getDocsUrl } from '../../utils/get-docs-url.js';
4
+ import { getRootIdentifier } from '../../utils/get-root-identifier.js';
5
+ import { NoRestDestructuringUtils } from './no-rest-destructuring.utils.js';
6
+
7
+ const name = 'no-rest-destructuring';
8
+ const createRule = ESLintUtils.RuleCreator(getDocsUrl);
9
+ const queryHooks = [
10
+ 'useQuery',
11
+ 'useQueries',
12
+ 'useInfiniteQuery',
13
+ 'useSuspenseQuery',
14
+ 'useSuspenseQueries',
15
+ 'useSuspenseInfiniteQuery'
16
+ ];
17
+ const rule = createRule({
18
+ name,
19
+ meta: {
20
+ type: 'problem',
21
+ docs: {
22
+ description: 'Disallows rest destructuring in queries',
23
+ recommended: 'warn'
24
+ },
25
+ messages: {
26
+ objectRestDestructure: `Object rest destructuring on a query will observe all changes to the query, leading to excessive re-renders.`
27
+ },
28
+ schema: [
29
+ {
30
+ type: 'object',
31
+ properties: {
32
+ clientNamePattern: {
33
+ type: 'string'
34
+ }
35
+ },
36
+ additionalProperties: false
37
+ }
38
+ ]
39
+ },
40
+ defaultOptions: [
41
+ {}
42
+ ],
43
+ create: (context)=>{
44
+ const option = context.options[0] ?? {};
45
+ const clientRegex = createClientNameRegex(option.clientNamePattern);
46
+ const queryResultVariables = new Set();
47
+ return {
48
+ CallExpression: (node)=>{
49
+ if (node.parent.type !== AST_NODE_TYPES.VariableDeclarator) return;
50
+ const callee = node.callee;
51
+ if (callee.type !== AST_NODE_TYPES.MemberExpression) return;
52
+ const hookName = getHookNameIfTargetFromClient(clientRegex, callee);
53
+ if (!hookName) return;
54
+ const returnValue = node.parent.id;
55
+ if (hookName !== 'useQueries' && hookName !== 'useSuspenseQueries') {
56
+ if (NoRestDestructuringUtils.isObjectRestDestructuring(returnValue)) {
57
+ return context.report({
58
+ node: node.parent,
59
+ messageId: 'objectRestDestructure'
60
+ });
61
+ }
62
+ if (returnValue.type === AST_NODE_TYPES.Identifier) {
63
+ queryResultVariables.add(returnValue.name);
64
+ }
65
+ return;
66
+ }
67
+ if (returnValue.type !== AST_NODE_TYPES.ArrayPattern) {
68
+ if (returnValue.type === AST_NODE_TYPES.Identifier) {
69
+ queryResultVariables.add(returnValue.name);
70
+ }
71
+ return;
72
+ }
73
+ returnValue.elements.forEach((queryResult)=>{
74
+ if (queryResult === null) {
75
+ return;
76
+ }
77
+ if (NoRestDestructuringUtils.isObjectRestDestructuring(queryResult)) {
78
+ context.report({
79
+ node: queryResult,
80
+ messageId: 'objectRestDestructure'
81
+ });
82
+ }
83
+ });
84
+ },
85
+ VariableDeclarator: (node)=>{
86
+ if (node.init?.type === AST_NODE_TYPES.Identifier && queryResultVariables.has(node.init.name) && NoRestDestructuringUtils.isObjectRestDestructuring(node.id)) {
87
+ context.report({
88
+ node,
89
+ messageId: 'objectRestDestructure'
90
+ });
91
+ }
92
+ },
93
+ SpreadElement: (node)=>{
94
+ if (node.argument.type === AST_NODE_TYPES.Identifier && queryResultVariables.has(node.argument.name)) {
95
+ context.report({
96
+ node,
97
+ messageId: 'objectRestDestructure'
98
+ });
99
+ }
100
+ }
101
+ };
102
+ }
103
+ });
104
+ function getHookNameIfTargetFromClient(clientRegex, callee) {
105
+ if (callee.property.type !== AST_NODE_TYPES.Identifier) return;
106
+ const name = callee.property.name;
107
+ if (!queryHooks.includes(name)) return;
108
+ const root = getRootIdentifier(callee.object);
109
+ return root && clientRegex.test(root.name) ? name : undefined;
110
+ }
111
+
112
+ export { name, rule };
113
+ //# sourceMappingURL=no-rest-destructuring.rule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-rest-destructuring.rule.js","sources":["../../../../src/rules/no-rest-destructuring/no-rest-destructuring.rule.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport type { ExtraRuleDocs } from '../../types';\nimport { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';\nimport { createClientNameRegex } from '../../utils/create-client-name-regex';\nimport { getDocsUrl } from '../../utils/get-docs-url';\nimport { getRootIdentifier } from '../../utils/get-root-identifier';\nimport { NoRestDestructuringUtils } from './no-rest-destructuring.utils';\n\nexport const name = 'no-rest-destructuring';\n\nconst createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl);\n\nconst queryHooks = [\n 'useQuery',\n 'useQueries',\n 'useInfiniteQuery',\n 'useSuspenseQuery',\n 'useSuspenseQueries',\n 'useSuspenseInfiniteQuery',\n];\n\nexport const rule = createRule({\n name,\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallows rest destructuring in queries',\n recommended: 'warn',\n },\n messages: {\n objectRestDestructure: `Object rest destructuring on a query will observe all changes to the query, leading to excessive re-renders.`,\n },\n schema: [\n {\n type: 'object',\n properties: {\n clientNamePattern: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n },\n defaultOptions: [{}],\n\n create: (context) => {\n const option =\n (context.options[0] as { clientNamePattern?: string } | undefined) ?? {};\n\n const clientRegex = createClientNameRegex(option.clientNamePattern);\n\n const queryResultVariables = new Set<string>();\n\n return {\n CallExpression: (node) => {\n if (node.parent.type !== AST_NODE_TYPES.VariableDeclarator) return;\n\n const callee = node.callee;\n if (callee.type !== AST_NODE_TYPES.MemberExpression) return;\n const hookName = getHookNameIfTargetFromClient(clientRegex, callee);\n if (!hookName) return;\n const returnValue = node.parent.id;\n\n if (hookName !== 'useQueries' && hookName !== 'useSuspenseQueries') {\n if (NoRestDestructuringUtils.isObjectRestDestructuring(returnValue)) {\n return context.report({\n node: node.parent,\n messageId: 'objectRestDestructure',\n });\n }\n\n if (returnValue.type === AST_NODE_TYPES.Identifier) {\n queryResultVariables.add(returnValue.name);\n }\n\n return;\n }\n\n if (returnValue.type !== AST_NODE_TYPES.ArrayPattern) {\n if (returnValue.type === AST_NODE_TYPES.Identifier) {\n queryResultVariables.add(returnValue.name);\n }\n return;\n }\n\n returnValue.elements.forEach((queryResult) => {\n if (queryResult === null) {\n return;\n }\n if (NoRestDestructuringUtils.isObjectRestDestructuring(queryResult)) {\n context.report({\n node: queryResult,\n messageId: 'objectRestDestructure',\n });\n }\n });\n },\n\n VariableDeclarator: (node) => {\n if (\n node.init?.type === AST_NODE_TYPES.Identifier &&\n queryResultVariables.has(node.init.name) &&\n NoRestDestructuringUtils.isObjectRestDestructuring(node.id)\n ) {\n context.report({\n node,\n messageId: 'objectRestDestructure',\n });\n }\n },\n\n SpreadElement: (node) => {\n if (\n node.argument.type === AST_NODE_TYPES.Identifier &&\n queryResultVariables.has(node.argument.name)\n ) {\n context.report({\n node,\n messageId: 'objectRestDestructure',\n });\n }\n },\n };\n },\n});\n\nfunction getHookNameIfTargetFromClient(\n clientRegex: RegExp,\n callee: TSESTree.MemberExpression\n): string | undefined {\n if (callee.property.type !== AST_NODE_TYPES.Identifier) return;\n const name = callee.property.name;\n if (!queryHooks.includes(name)) return;\n const root = getRootIdentifier(callee.object);\n return root && clientRegex.test(root.name) ? name : undefined;\n}\n"],"names":["name","createRule","ESLintUtils","RuleCreator","getDocsUrl","queryHooks","rule","meta","type","docs","description","recommended","messages","objectRestDestructure","schema","properties","clientNamePattern","additionalProperties","defaultOptions","create","context","option","options","clientRegex","createClientNameRegex","queryResultVariables","Set","CallExpression","node","parent","AST_NODE_TYPES","VariableDeclarator","callee","MemberExpression","hookName","getHookNameIfTargetFromClient","returnValue","id","NoRestDestructuringUtils","isObjectRestDestructuring","report","messageId","Identifier","add","ArrayPattern","elements","forEach","queryResult","init","has","SpreadElement","argument","property","includes","root","getRootIdentifier","object","test","undefined"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+
3
+ const NoRestDestructuringUtils = {
4
+ isObjectRestDestructuring (node) {
5
+ if (node.type !== AST_NODE_TYPES.ObjectPattern) {
6
+ return false;
7
+ }
8
+ return node.properties.some((p)=>p.type === AST_NODE_TYPES.RestElement);
9
+ }
10
+ };
11
+
12
+ export { NoRestDestructuringUtils };
13
+ //# sourceMappingURL=no-rest-destructuring.utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-rest-destructuring.utils.js","sources":["../../../../src/rules/no-rest-destructuring/no-rest-destructuring.utils.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nexport const NoRestDestructuringUtils = {\n isObjectRestDestructuring(node: TSESTree.Node): boolean {\n if (node.type !== AST_NODE_TYPES.ObjectPattern) {\n return false;\n }\n return node.properties.some((p) => p.type === AST_NODE_TYPES.RestElement);\n },\n};\n"],"names":["NoRestDestructuringUtils","isObjectRestDestructuring","node","type","AST_NODE_TYPES","ObjectPattern","properties","some","p","RestElement"],"mappings":";;MAGaA,wBAA2B,GAAA;AACtCC,IAAAA,yBAAAA,CAAAA,CAA0BC,IAAmB,EAAA;AAC3C,QAAA,IAAIA,IAAKC,CAAAA,IAAI,KAAKC,cAAAA,CAAeC,aAAa,EAAE;YAC9C,OAAO,KAAA,CAAA;AACT,SAAA;QACA,OAAOH,IAAAA,CAAKI,UAAU,CAACC,IAAI,CAAC,CAACC,CAAAA,GAAMA,CAAEL,CAAAA,IAAI,KAAKC,cAAAA,CAAeK,WAAW,CAAA,CAAA;AAC1E,KAAA;AACF;;;;"}
@@ -0,0 +1,121 @@
1
+ import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { createClientNameRegex } from '../../utils/create-client-name-regex.js';
3
+ import { getDocsUrl } from '../../utils/get-docs-url.js';
4
+ import { getRootIdentifier } from '../../utils/get-root-identifier.js';
5
+
6
+ const name = 'no-unstable-deps';
7
+ const reactHookNames = [
8
+ 'useEffect',
9
+ 'useCallback',
10
+ 'useMemo'
11
+ ];
12
+ const useQueryHookNames = [
13
+ 'useQuery',
14
+ 'useSuspenseQuery',
15
+ 'useQueries',
16
+ 'useSuspenseQueries',
17
+ 'useInfiniteQuery',
18
+ 'useSuspenseInfiniteQuery'
19
+ ];
20
+ const allHookNames = [
21
+ 'useMutation',
22
+ ...useQueryHookNames
23
+ ];
24
+ const createRule = ESLintUtils.RuleCreator(getDocsUrl);
25
+ const rule = createRule({
26
+ name,
27
+ meta: {
28
+ type: 'problem',
29
+ docs: {
30
+ description: 'Disallow putting the result of query hooks directly in a React hook dependency array',
31
+ recommended: 'error'
32
+ },
33
+ messages: {
34
+ noUnstableDeps: `The result of {{queryHook}} is not referentially stable, so don't pass it directly into the dependencies array of {{reactHook}}. Instead, destructure the return value of {{queryHook}} and pass the destructured values into the dependency array of {{reactHook}}.`
35
+ },
36
+ schema: [
37
+ {
38
+ type: 'object',
39
+ properties: {
40
+ clientNamePattern: {
41
+ type: 'string'
42
+ }
43
+ },
44
+ additionalProperties: false
45
+ }
46
+ ]
47
+ },
48
+ defaultOptions: [
49
+ {}
50
+ ],
51
+ create: (context)=>{
52
+ const option = context.options[0] ?? {};
53
+ const clientRegex = createClientNameRegex(option.clientNamePattern);
54
+ const trackedVariables = {};
55
+ const hookAliasMap = {};
56
+ function getReactHook(node) {
57
+ if (node.callee.type === 'Identifier') {
58
+ const calleeName = node.callee.name;
59
+ if (reactHookNames.includes(calleeName) || calleeName in hookAliasMap) {
60
+ // Return the actually used identifier (could be alias)
61
+ return calleeName;
62
+ }
63
+ } else if (node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && node.callee.object.name === 'React' && node.callee.property.type === 'Identifier' && reactHookNames.includes(node.callee.property.name)) {
64
+ return node.callee.property.name;
65
+ }
66
+ return undefined;
67
+ }
68
+ return {
69
+ ImportDeclaration (node) {
70
+ if (node.specifiers.length > 0 && node.importKind === 'value' && node.source.value === 'React') {
71
+ node.specifiers.forEach((specifier)=>{
72
+ if (specifier.type === AST_NODE_TYPES.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES.Identifier && reactHookNames.includes(specifier.imported.name)) {
73
+ hookAliasMap[specifier.local.name] = specifier.imported.name;
74
+ }
75
+ });
76
+ }
77
+ },
78
+ VariableDeclarator (node) {
79
+ if (node.init?.type !== AST_NODE_TYPES.CallExpression) return;
80
+ const hook = isQueryHookFromClient(clientRegex, node.init);
81
+ if (!hook) return;
82
+ if (node.id.type === AST_NODE_TYPES.Identifier) {
83
+ trackedVariables[node.id.name] = hook;
84
+ }
85
+ },
86
+ CallExpression: (node)=>{
87
+ const reactHook = getReactHook(node);
88
+ if (!reactHook) return;
89
+ if (node.arguments.length <= 1) return;
90
+ if (node.arguments[1]?.type !== AST_NODE_TYPES.ArrayExpression) return;
91
+ const depsArray = node.arguments[1].elements;
92
+ depsArray.forEach((dep)=>{
93
+ if (dep?.type !== AST_NODE_TYPES.Identifier) return;
94
+ const queryHook = trackedVariables[dep.name];
95
+ if (!queryHook) return;
96
+ context.report({
97
+ node: dep,
98
+ messageId: 'noUnstableDeps',
99
+ data: {
100
+ queryHook,
101
+ reactHook
102
+ }
103
+ });
104
+ });
105
+ }
106
+ };
107
+ }
108
+ });
109
+ function isQueryHookFromClient(clientRegex, call) {
110
+ if (call.callee.type !== AST_NODE_TYPES.MemberExpression) return null;
111
+ const member = call.callee;
112
+ if (member.property.type !== AST_NODE_TYPES.Identifier) return null;
113
+ const hook = member.property.name;
114
+ if (!allHookNames.includes(hook)) return null;
115
+ const root = getRootIdentifier(member.object);
116
+ if (!root) return null;
117
+ return clientRegex.test(root.name) ? hook : null;
118
+ }
119
+
120
+ export { name, reactHookNames, rule, useQueryHookNames };
121
+ //# sourceMappingURL=no-unstable-deps.rule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unstable-deps.rule.js","sources":["../../../../src/rules/no-unstable-deps/no-unstable-deps.rule.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport type { ExtraRuleDocs } from '../../types';\nimport { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';\nimport { createClientNameRegex } from '../../utils/create-client-name-regex';\nimport { getDocsUrl } from '../../utils/get-docs-url';\nimport { getRootIdentifier } from '../../utils/get-root-identifier';\n\nexport const name = 'no-unstable-deps';\n\nexport const reactHookNames = ['useEffect', 'useCallback', 'useMemo'];\nexport const useQueryHookNames = [\n 'useQuery',\n 'useSuspenseQuery',\n 'useQueries',\n 'useSuspenseQueries',\n 'useInfiniteQuery',\n 'useSuspenseInfiniteQuery',\n];\nconst allHookNames = ['useMutation', ...useQueryHookNames];\nconst createRule = ESLintUtils.RuleCreator<ExtraRuleDocs>(getDocsUrl);\n\nexport const rule = createRule({\n name,\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Disallow putting the result of query hooks directly in a React hook dependency array',\n recommended: 'error',\n },\n messages: {\n noUnstableDeps: `The result of {{queryHook}} is not referentially stable, so don't pass it directly into the dependencies array of {{reactHook}}. Instead, destructure the return value of {{queryHook}} and pass the destructured values into the dependency array of {{reactHook}}.`,\n },\n schema: [\n {\n type: 'object',\n properties: {\n clientNamePattern: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n },\n defaultOptions: [{}],\n\n create: (context) => {\n const option =\n (context.options[0] as { clientNamePattern?: string } | undefined) ?? {};\n const clientRegex = createClientNameRegex(option.clientNamePattern);\n\n const trackedVariables: Record<string, string> = {};\n const hookAliasMap: Record<string, string> = {};\n\n function getReactHook(node: TSESTree.CallExpression): string | undefined {\n if (node.callee.type === 'Identifier') {\n const calleeName = node.callee.name;\n if (reactHookNames.includes(calleeName) || calleeName in hookAliasMap) {\n // Return the actually used identifier (could be alias)\n return calleeName;\n }\n } else if (\n node.callee.type === 'MemberExpression' &&\n node.callee.object.type === 'Identifier' &&\n node.callee.object.name === 'React' &&\n node.callee.property.type === 'Identifier' &&\n reactHookNames.includes(node.callee.property.name)\n ) {\n return node.callee.property.name;\n }\n return undefined;\n }\n\n return {\n ImportDeclaration(node: TSESTree.ImportDeclaration) {\n if (\n node.specifiers.length > 0 &&\n node.importKind === 'value' &&\n node.source.value === 'React'\n ) {\n node.specifiers.forEach((specifier) => {\n if (\n specifier.type === AST_NODE_TYPES.ImportSpecifier &&\n specifier.imported.type === AST_NODE_TYPES.Identifier &&\n reactHookNames.includes(specifier.imported.name)\n ) {\n hookAliasMap[specifier.local.name] = specifier.imported.name;\n }\n });\n }\n },\n\n VariableDeclarator(node) {\n if (node.init?.type !== AST_NODE_TYPES.CallExpression) return;\n const hook = isQueryHookFromClient(clientRegex, node.init);\n if (!hook) return;\n if (node.id.type === AST_NODE_TYPES.Identifier) {\n trackedVariables[node.id.name] = hook;\n }\n },\n CallExpression: (node) => {\n const reactHook = getReactHook(node);\n if (!reactHook) return;\n if (node.arguments.length <= 1) return;\n if (node.arguments[1]?.type !== AST_NODE_TYPES.ArrayExpression) return;\n\n const depsArray = node.arguments[1].elements;\n depsArray.forEach((dep) => {\n if (dep?.type !== AST_NODE_TYPES.Identifier) return;\n const queryHook = trackedVariables[dep.name];\n if (!queryHook) return;\n context.report({\n node: dep,\n messageId: 'noUnstableDeps',\n data: {\n queryHook,\n reactHook,\n },\n });\n });\n },\n };\n },\n});\n\nfunction isQueryHookFromClient(\n clientRegex: RegExp,\n call: TSESTree.CallExpression\n): string | null {\n if (call.callee.type !== AST_NODE_TYPES.MemberExpression) return null;\n const member = call.callee;\n if (member.property.type !== AST_NODE_TYPES.Identifier) return null;\n const hook = member.property.name;\n if (!allHookNames.includes(hook)) return null;\n const root = getRootIdentifier(member.object);\n if (!root) return null;\n return clientRegex.test(root.name) ? hook : null;\n}\n"],"names":["name","reactHookNames","useQueryHookNames","allHookNames","createRule","ESLintUtils","RuleCreator","getDocsUrl","rule","meta","type","docs","description","recommended","messages","noUnstableDeps","schema","properties","clientNamePattern","additionalProperties","defaultOptions","create","context","option","options","clientRegex","createClientNameRegex","trackedVariables","hookAliasMap","getReactHook","node","callee","calleeName","includes","object","property","undefined","ImportDeclaration","specifiers","length","importKind","source","value","forEach","specifier","AST_NODE_TYPES","ImportSpecifier","imported","Identifier","local","VariableDeclarator","init","CallExpression","hook","isQueryHookFromClient","id","reactHook","arguments","ArrayExpression","depsArray","elements","dep","queryHook","report","messageId","data","call","MemberExpression","member","root","getRootIdentifier","test"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { name, rule } from './rules/no-rest-destructuring/no-rest-destructuring.rule.js';
2
+ import { name as name$1, rule as rule$1 } from './rules/no-unstable-deps/no-unstable-deps.rule.js';
3
+
4
+ const rules = {
5
+ [name]: rule,
6
+ [name$1]: rule$1
7
+ };
8
+
9
+ export { rules };
10
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sources":["../../src/rules.ts"],"sourcesContent":["import type { ESLintUtils } from '@typescript-eslint/utils';\nimport type { ExtraRuleDocs } from './types';\nimport * as noRestDestructuring from './rules/no-rest-destructuring/no-rest-destructuring.rule';\nimport * as noUnstableDeps from './rules/no-unstable-deps/no-unstable-deps.rule';\n\nexport const rules: Record<\n string,\n ESLintUtils.RuleModule<\n string,\n ReadonlyArray<unknown>,\n ExtraRuleDocs,\n ESLintUtils.RuleListener\n >\n> = {\n [noRestDestructuring.name]: noRestDestructuring.rule,\n [noUnstableDeps.name]: noUnstableDeps.rule,\n};\n"],"names":["rules","noRestDestructuring","noUnstableDeps"],"mappings":";;;MAKaA,KAQT,GAAA;AACF,IAAA,CAACC,IAAwB,GAAGA,IAAwB;AACpD,IAAA,CAACC,MAAmB,GAAGA,MAAmB;AAC5C;;;;"}
@@ -0,0 +1,25 @@
1
+ function createClientNameRegex(clientNamePattern) {
2
+ clientNamePattern = clientNamePattern?.trim();
3
+ if (clientNamePattern) {
4
+ // Check if pattern is in regex format: /pattern/flags
5
+ const regexMatch = clientNamePattern.match(/^\/(.*)\/([gimuy]*)$/);
6
+ if (regexMatch) {
7
+ const regexPattern = regexMatch[1].trim();
8
+ const flags = regexMatch[2];
9
+ // Validate that regex pattern is not empty or whitespace-only
10
+ if (regexPattern === '') {
11
+ throw new Error('clientNamePattern cannot be empty or contain only whitespace');
12
+ }
13
+ return new RegExp(regexPattern, flags);
14
+ }
15
+ // Fallback to treating as plain string pattern
16
+ return new RegExp(clientNamePattern);
17
+ } else if (clientNamePattern === '') {
18
+ throw new Error('clientNamePattern cannot be empty or contain only whitespace');
19
+ }
20
+ // Default if no pattern provided
21
+ return /qraft|api/i;
22
+ }
23
+
24
+ export { createClientNameRegex };
25
+ //# sourceMappingURL=create-client-name-regex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-client-name-regex.js","sources":["../../../src/utils/create-client-name-regex.ts"],"sourcesContent":["export function createClientNameRegex(clientNamePattern: string | undefined) {\n clientNamePattern = clientNamePattern?.trim();\n\n if (clientNamePattern) {\n // Check if pattern is in regex format: /pattern/flags\n const regexMatch = clientNamePattern.match(/^\\/(.*)\\/([gimuy]*)$/);\n\n if (regexMatch) {\n const regexPattern = regexMatch[1].trim();\n const flags = regexMatch[2];\n\n // Validate that regex pattern is not empty or whitespace-only\n if (regexPattern === '') {\n throw new Error(\n 'clientNamePattern cannot be empty or contain only whitespace'\n );\n }\n\n return new RegExp(regexPattern, flags);\n }\n\n // Fallback to treating as plain string pattern\n return new RegExp(clientNamePattern);\n } else if (clientNamePattern === '') {\n throw new Error(\n 'clientNamePattern cannot be empty or contain only whitespace'\n );\n }\n\n // Default if no pattern provided\n return /qraft|api/i;\n}\n"],"names":["createClientNameRegex","clientNamePattern","trim","regexMatch","match","regexPattern","flags","Error","RegExp"],"mappings":"AAAO,SAASA,sBAAsBC,iBAAqC,EAAA;AACzEA,IAAAA,iBAAAA,GAAoBA,iBAAmBC,EAAAA,IAAAA,EAAAA,CAAAA;AAEvC,IAAA,IAAID,iBAAmB,EAAA;;QAErB,MAAME,UAAAA,GAAaF,iBAAkBG,CAAAA,KAAK,CAAC,sBAAA,CAAA,CAAA;AAE3C,QAAA,IAAID,UAAY,EAAA;AACd,YAAA,MAAME,YAAeF,GAAAA,UAAU,CAAC,CAAA,CAAE,CAACD,IAAI,EAAA,CAAA;YACvC,MAAMI,KAAAA,GAAQH,UAAU,CAAC,CAAE,CAAA,CAAA;;AAG3B,YAAA,IAAIE,iBAAiB,EAAI,EAAA;AACvB,gBAAA,MAAM,IAAIE,KACR,CAAA,8DAAA,CAAA,CAAA;AAEJ,aAAA;YAEA,OAAO,IAAIC,OAAOH,YAAcC,EAAAA,KAAAA,CAAAA,CAAAA;AAClC,SAAA;;AAGA,QAAA,OAAO,IAAIE,MAAOP,CAAAA,iBAAAA,CAAAA,CAAAA;KACb,MAAA,IAAIA,sBAAsB,EAAI,EAAA;AACnC,QAAA,MAAM,IAAIM,KACR,CAAA,8DAAA,CAAA,CAAA;AAEJ,KAAA;;IAGA,OAAO,YAAA,CAAA;AACT;;;;"}
@@ -0,0 +1,4 @@
1
+ const getDocsUrl = (ruleName)=>`https://openapi-qraft.github.io/openapi-qraft/docs/eslint/${ruleName}`;
2
+
3
+ export { getDocsUrl };
4
+ //# sourceMappingURL=get-docs-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-docs-url.js","sources":["../../../src/utils/get-docs-url.ts"],"sourcesContent":["export const getDocsUrl = (ruleName: string): string =>\n `https://openapi-qraft.github.io/openapi-qraft/docs/eslint/${ruleName}`;\n"],"names":["getDocsUrl","ruleName"],"mappings":"AAAO,MAAMA,aAAa,CAACC,QAAAA,GACzB,CAAC,0DAA0D,EAAEA,QAAS,CAAA;;;;"}
@@ -0,0 +1,20 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+
3
+ function getRootIdentifier(node) {
4
+ let current = node;
5
+ while(current.type === AST_NODE_TYPES.MemberExpression || current.type === AST_NODE_TYPES.TSNonNullExpression || current.type === AST_NODE_TYPES.ChainExpression){
6
+ if (current.type === AST_NODE_TYPES.MemberExpression) {
7
+ current = current.object;
8
+ continue;
9
+ }
10
+ if (current.type === AST_NODE_TYPES.TSNonNullExpression) {
11
+ current = current.expression;
12
+ continue;
13
+ }
14
+ current = current.expression;
15
+ }
16
+ return current.type === AST_NODE_TYPES.Identifier ? current : undefined;
17
+ }
18
+
19
+ export { getRootIdentifier };
20
+ //# sourceMappingURL=get-root-identifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-root-identifier.js","sources":["../../../src/utils/get-root-identifier.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { AST_NODE_TYPES } from '@typescript-eslint/utils';\n\nexport function getRootIdentifier(\n node: TSESTree.Node\n): TSESTree.Identifier | undefined {\n let current: TSESTree.Node | undefined = node;\n while (\n current.type === AST_NODE_TYPES.MemberExpression ||\n current.type === AST_NODE_TYPES.TSNonNullExpression ||\n current.type === AST_NODE_TYPES.ChainExpression\n ) {\n if (current.type === AST_NODE_TYPES.MemberExpression) {\n current = current.object;\n continue;\n }\n if (current.type === AST_NODE_TYPES.TSNonNullExpression) {\n current = current.expression;\n continue;\n }\n\n current = current.expression;\n }\n return current.type === AST_NODE_TYPES.Identifier ? current : undefined;\n}\n"],"names":["getRootIdentifier","node","current","type","AST_NODE_TYPES","MemberExpression","TSNonNullExpression","ChainExpression","object","expression","Identifier","undefined"],"mappings":";;AAGO,SAASA,kBACdC,IAAmB,EAAA;AAEnB,IAAA,IAAIC,OAAqCD,GAAAA,IAAAA,CAAAA;AACzC,IAAA,MACEC,QAAQC,IAAI,KAAKC,cAAeC,CAAAA,gBAAgB,IAChDH,OAAQC,CAAAA,IAAI,KAAKC,cAAAA,CAAeE,mBAAmB,IACnDJ,OAAAA,CAAQC,IAAI,KAAKC,cAAAA,CAAeG,eAAe,CAC/C;AACA,QAAA,IAAIL,OAAQC,CAAAA,IAAI,KAAKC,cAAAA,CAAeC,gBAAgB,EAAE;AACpDH,YAAAA,OAAAA,GAAUA,QAAQM,MAAM,CAAA;AACxB,YAAA,SAAA;AACF,SAAA;AACA,QAAA,IAAIN,OAAQC,CAAAA,IAAI,KAAKC,cAAAA,CAAeE,mBAAmB,EAAE;AACvDJ,YAAAA,OAAAA,GAAUA,QAAQO,UAAU,CAAA;AAC5B,YAAA,SAAA;AACF,SAAA;AAEAP,QAAAA,OAAAA,GAAUA,QAAQO,UAAU,CAAA;AAC9B,KAAA;AACA,IAAA,OAAOP,QAAQC,IAAI,KAAKC,cAAeM,CAAAA,UAAU,GAAGR,OAAUS,GAAAA,SAAAA,CAAAA;AAChE;;;;"}
@@ -0,0 +1,23 @@
1
+ import type { RuleModule } from '@typescript-eslint/utils/ts-eslint';
2
+ import type { ESLint, Linter } from 'eslint';
3
+ import { rules } from './rules';
4
+ type RuleKey = keyof typeof rules;
5
+ export interface Plugin extends Omit<ESLint.Plugin, 'rules'> {
6
+ rules: Record<RuleKey, RuleModule<any, any, any>>;
7
+ configs: {
8
+ recommended: ESLint.ConfigData;
9
+ 'flat/recommended': Array<Linter.Config>;
10
+ };
11
+ }
12
+ export declare const plugin: Plugin;
13
+ export { rules };
14
+ export declare const configs: {
15
+ recommended: ESLint.ConfigData;
16
+ 'flat/recommended': Array<Linter.Config>;
17
+ };
18
+ /**
19
+ * @alias
20
+ * @type {Plugin}
21
+ **/
22
+ export default plugin;
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,KAAK,OAAO,GAAG,MAAM,OAAO,KAAK,CAAC;AAElC,MAAM,WAAW,MAAO,SAAQ,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC1D,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IAClD,OAAO,EAAE;QACP,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;QAC/B,kBAAkB,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;KAC1C,CAAC;CACH;AAED,eAAO,MAAM,MAAM,EAAE,MAMpB,CAAC;AA0BF,OAAO,EAAE,KAAK,EAAE,CAAC;AACjB,eAAO,MAAM,OAAO;iBAtCH,MAAM,CAAC,UAAU;wBACV,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;CAqCP,CAAC;AAEtC;;;IAGI;AACJ,eAAe,MAAM,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { ExtraRuleDocs } from '../../types';
2
+ import { ESLintUtils } from '@typescript-eslint/utils';
3
+ export declare const name = "no-rest-destructuring";
4
+ export declare const rule: ESLintUtils.RuleModule<"objectRestDestructure", [{}], ExtraRuleDocs, ESLintUtils.RuleListener>;
5
+ //# sourceMappingURL=no-rest-destructuring.rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-rest-destructuring.rule.d.ts","sourceRoot":"","sources":["../../../../src/rules/no-rest-destructuring/no-rest-destructuring.rule.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAkB,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAMvE,eAAO,MAAM,IAAI,0BAA0B,CAAC;AAa5C,eAAO,MAAM,IAAI,gGAsGf,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { TSESTree } from '@typescript-eslint/utils';
2
+ export declare const NoRestDestructuringUtils: {
3
+ isObjectRestDestructuring(node: TSESTree.Node): boolean;
4
+ };
5
+ //# sourceMappingURL=no-rest-destructuring.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-rest-destructuring.utils.d.ts","sourceRoot":"","sources":["../../../../src/rules/no-rest-destructuring/no-rest-destructuring.utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAGzD,eAAO,MAAM,wBAAwB;oCACH,QAAQ,CAAC,IAAI,GAAG,OAAO;CAMxD,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { ExtraRuleDocs } from '../../types';
2
+ import { ESLintUtils } from '@typescript-eslint/utils';
3
+ export declare const name = "no-unstable-deps";
4
+ export declare const reactHookNames: string[];
5
+ export declare const useQueryHookNames: string[];
6
+ export declare const rule: ESLintUtils.RuleModule<"noUnstableDeps", [{}], ExtraRuleDocs, ESLintUtils.RuleListener>;
7
+ //# sourceMappingURL=no-unstable-deps.rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unstable-deps.rule.d.ts","sourceRoot":"","sources":["../../../../src/rules/no-unstable-deps/no-unstable-deps.rule.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAkB,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAKvE,eAAO,MAAM,IAAI,qBAAqB,CAAC;AAEvC,eAAO,MAAM,cAAc,UAA0C,CAAC;AACtE,eAAO,MAAM,iBAAiB,UAO7B,CAAC;AAIF,eAAO,MAAM,IAAI,yFAqGf,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { ESLintUtils } from '@typescript-eslint/utils';
2
+ import type { ExtraRuleDocs } from './types';
3
+ export declare const rules: Record<string, ESLintUtils.RuleModule<string, ReadonlyArray<unknown>, ExtraRuleDocs, ESLintUtils.RuleListener>>;
4
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAI7C,eAAO,MAAM,KAAK,EAAE,MAAM,CACxB,MAAM,EACN,WAAW,CAAC,UAAU,CACpB,MAAM,EACN,aAAa,CAAC,OAAO,CAAC,EACtB,aAAa,EACb,WAAW,CAAC,YAAY,CACzB,CAIF,CAAC"}
@@ -0,0 +1,4 @@
1
+ export type ExtraRuleDocs = {
2
+ recommended: 'strict' | 'error' | 'warn';
3
+ };
4
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG;IAC1B,WAAW,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;CAC1C,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function createClientNameRegex(clientNamePattern: string | undefined): RegExp;
2
+ //# sourceMappingURL=create-client-name-regex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-client-name-regex.d.ts","sourceRoot":"","sources":["../../../src/utils/create-client-name-regex.ts"],"names":[],"mappings":"AAAA,wBAAgB,qBAAqB,CAAC,iBAAiB,EAAE,MAAM,GAAG,SAAS,UA+B1E"}
@@ -0,0 +1,2 @@
1
+ export declare const getDocsUrl: (ruleName: string) => string;
2
+ //# sourceMappingURL=get-docs-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-docs-url.d.ts","sourceRoot":"","sources":["../../../src/utils/get-docs-url.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU,aAAc,MAAM,KAAG,MAC2B,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { TSESTree } from '@typescript-eslint/utils';
2
+ export declare function getRootIdentifier(node: TSESTree.Node): TSESTree.Identifier | undefined;
3
+ //# sourceMappingURL=get-root-identifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-root-identifier.d.ts","sourceRoot":"","sources":["../../../src/utils/get-root-identifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAGzD,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,QAAQ,CAAC,IAAI,GAClB,QAAQ,CAAC,UAAU,GAAG,SAAS,CAmBjC"}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@openapi-qraft/eslint-plugin-query",
3
+ "version": "2.12.0",
4
+ "description": "ESLint plugin for OpenAPI Qraft",
5
+ "homepage": "https://openapi-qraft.github.io/openapi-qraft/docs/eslint/eslint-plugin-query",
6
+ "packageManager": "yarn@4.0.2",
7
+ "license": "MIT",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/OpenAPI-Qraft/openapi-qraft.git",
14
+ "directory": "packages/eslint-plugin-query"
15
+ },
16
+ "scripts": {
17
+ "build": "yarn clean && NODE_ENV=production rollup --config rollup.config.mjs && tsc --project tsconfig.build.json --emitDeclarationOnly",
18
+ "typecheck": "tsc --noEmit",
19
+ "lint": "eslint",
20
+ "test": "vitest run --coverage",
21
+ "clean": "rimraf dist/"
22
+ },
23
+ "type": "module",
24
+ "types": "dist/types/index.d.ts",
25
+ "module": "dist/esm/index.js",
26
+ "main": "dist/cjs/index.cjs",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/types/index.d.ts",
30
+ "import": "./dist/esm/index.js",
31
+ "require": "./dist/cjs/index.cjs"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
35
+ "typesVersions": {
36
+ "*": {
37
+ "*": [
38
+ "dist/types/*"
39
+ ]
40
+ }
41
+ },
42
+ "sideEffects": false,
43
+ "files": [
44
+ "dist",
45
+ "src",
46
+ "!src/__tests__"
47
+ ],
48
+ "dependencies": {
49
+ "@typescript-eslint/utils": "^8.37.0"
50
+ },
51
+ "devDependencies": {
52
+ "@openapi-qraft/eslint-config": "1.0.1",
53
+ "@openapi-qraft/rollup-config": "1.1.1",
54
+ "@types/node": "^20.16.5",
55
+ "@typescript-eslint/rule-tester": "^8.37.0",
56
+ "@typescript-eslint/typescript-estree": "^8.39.1",
57
+ "@vitest/coverage-v8": "^3.0.5",
58
+ "eslint": "^9.24.0",
59
+ "rimraf": "^5.0.10",
60
+ "rollup": "~4.22.4",
61
+ "typescript": "^5.6.2",
62
+ "vitest": "^3.0.5"
63
+ },
64
+ "peerDependencies": {
65
+ "eslint": "^8.57.0 || ^9.0.0"
66
+ }
67
+ }
package/src/index.ts ADDED
@@ -0,0 +1,54 @@
1
+ import type { RuleModule } from '@typescript-eslint/utils/ts-eslint';
2
+ import type { ESLint, Linter } from 'eslint';
3
+ import { rules } from './rules';
4
+
5
+ type RuleKey = keyof typeof rules;
6
+
7
+ export interface Plugin extends Omit<ESLint.Plugin, 'rules'> {
8
+ rules: Record<RuleKey, RuleModule<any, any, any>>;
9
+ configs: {
10
+ recommended: ESLint.ConfigData;
11
+ 'flat/recommended': Array<Linter.Config>;
12
+ };
13
+ }
14
+
15
+ export const plugin: Plugin = {
16
+ meta: {
17
+ name: '@openapi-qraft/eslint-plugin-query',
18
+ },
19
+ configs: {} as Plugin['configs'],
20
+ rules,
21
+ };
22
+
23
+ // Assign configs here so we can reference `plugin`
24
+ Object.assign(plugin.configs, {
25
+ recommended: {
26
+ plugins: ['@openapi-qraft/query'],
27
+ rules: {
28
+ '@openapi-qraft/query/no-rest-destructuring': 'warn',
29
+ '@openapi-qraft/query/no-unstable-deps': 'error',
30
+ },
31
+ },
32
+ 'flat/recommended': [
33
+ {
34
+ name: 'openapi-qraft/query/flat/recommended',
35
+ plugins: {
36
+ '@openapi-qraft/query': plugin,
37
+ },
38
+ rules: {
39
+ '@openapi-qraft/query/no-rest-destructuring': 'warn',
40
+ '@openapi-qraft/query/no-unstable-deps': 'error',
41
+ },
42
+ },
43
+ ],
44
+ });
45
+
46
+ // Reexport rules & configs for the Legacy config
47
+ export { rules };
48
+ export const configs = plugin.configs;
49
+
50
+ /**
51
+ * @alias
52
+ * @type {Plugin}
53
+ **/
54
+ export default plugin;