@blumintinc/eslint-plugin-blumint 1.12.6 → 1.13.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.
- package/README.md +140 -99
- package/lib/index.js +111 -17
- package/lib/rules/array-methods-this-context.js +20 -2
- package/lib/rules/avoid-utils-directory.js +11 -2
- package/lib/rules/dynamic-https-errors.d.ts +3 -1
- package/lib/rules/dynamic-https-errors.js +48 -10
- package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
- package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +51 -95
- package/lib/rules/enforce-assert-throws.d.ts +3 -1
- package/lib/rules/enforce-assert-throws.js +493 -51
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
- package/lib/rules/enforce-boolean-naming-prefixes.js +832 -39
- package/lib/rules/enforce-callable-types.js +4 -4
- package/lib/rules/enforce-callback-memo.js +143 -18
- package/lib/rules/enforce-centralized-mock-firestore.js +4 -1
- package/lib/rules/enforce-console-error.d.ts +3 -0
- package/lib/rules/enforce-console-error.js +250 -0
- package/lib/rules/enforce-css-media-queries.js +24 -46
- package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
- package/lib/rules/enforce-dynamic-imports.js +1 -1
- package/lib/rules/enforce-exported-function-types.js +3 -3
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +212 -0
- package/lib/rules/enforce-firestore-doc-ref-generic.js +335 -0
- package/lib/rules/enforce-firestore-facade.js +347 -157
- package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
- package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
- package/lib/rules/enforce-global-constants.d.ts +4 -1
- package/lib/rules/enforce-global-constants.js +240 -12
- package/lib/rules/enforce-memoize-async.d.ts +2 -1
- package/lib/rules/enforce-memoize-async.js +96 -27
- package/lib/rules/enforce-memoize-getters.d.ts +3 -0
- package/lib/rules/enforce-memoize-getters.js +123 -0
- package/lib/rules/enforce-microdiff.js +2 -1
- package/lib/rules/enforce-positive-naming.js +5 -7
- package/lib/rules/enforce-props-argument-name.d.ts +2 -7
- package/lib/rules/enforce-props-argument-name.js +165 -151
- package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
- package/lib/rules/enforce-props-naming-consistency.js +145 -0
- package/lib/rules/enforce-querykey-ts.d.ts +9 -0
- package/lib/rules/enforce-querykey-ts.js +274 -0
- package/lib/rules/enforce-singular-type-names.js +9 -5
- package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.js +96 -0
- package/lib/rules/enforce-verb-noun-naming.js +85 -10
- package/lib/rules/extract-global-constants.js +57 -45
- package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
- package/lib/rules/fast-deep-equal-over-microdiff.js +356 -78
- package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
- package/lib/rules/firestore-transaction-reads-before-writes.js +231 -0
- package/lib/rules/generic-starts-with-t.js +8 -2
- package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
- package/lib/rules/memoize-root-level-hocs.js +242 -0
- package/lib/rules/no-array-length-in-deps.d.ts +12 -0
- package/lib/rules/no-array-length-in-deps.js +308 -0
- package/lib/rules/no-async-array-filter.js +5 -2
- package/lib/rules/no-complex-cloud-params.js +15 -2
- package/lib/rules/no-compositing-layer-props.js +4 -2
- package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
- package/lib/rules/no-entire-object-hook-deps.js +317 -63
- package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
- package/lib/rules/no-excessive-parent-chain.js +273 -0
- package/lib/rules/no-firestore-object-arrays.js +345 -30
- package/lib/rules/no-hungarian.js +13 -13
- package/lib/rules/no-margin-properties.d.ts +7 -1
- package/lib/rules/no-margin-properties.js +16 -3
- package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
- package/lib/rules/no-misleading-boolean-prefixes.js +327 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.js +260 -0
- package/lib/rules/no-passthrough-getters.d.ts +4 -0
- package/lib/rules/no-passthrough-getters.js +170 -0
- package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
- package/lib/rules/no-redundant-usecallback-wrapper.js +315 -0
- package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
- package/lib/rules/no-restricted-properties-fix.js +172 -0
- package/lib/rules/no-separate-loading-state.d.ts +8 -0
- package/lib/rules/no-separate-loading-state.js +142 -0
- package/lib/rules/no-stale-state-across-await.d.ts +1 -0
- package/lib/rules/no-stale-state-across-await.js +185 -0
- package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
- package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
- package/lib/rules/no-type-assertion-returns.js +6 -0
- package/lib/rules/no-undefined-null-passthrough.js +166 -19
- package/lib/rules/no-unnecessary-verb-suffix.js +0 -2
- package/lib/rules/no-unsafe-firestore-spread.js +8 -5
- package/lib/rules/no-unused-props.d.ts +3 -0
- package/lib/rules/no-unused-props.js +213 -78
- package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
- package/lib/rules/optimize-object-boolean-conditions.js +234 -0
- package/lib/rules/parallelize-async-operations.d.ts +8 -0
- package/lib/rules/parallelize-async-operations.js +375 -0
- package/lib/rules/prefer-batch-operations.js +199 -33
- package/lib/rules/prefer-block-comments-for-declarations.js +13 -3
- package/lib/rules/prefer-document-flattening.d.ts +4 -0
- package/lib/rules/prefer-document-flattening.js +239 -0
- package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
- package/lib/rules/prefer-field-paths-in-transforms.js +250 -0
- package/lib/rules/prefer-fragment-component.js +3 -3
- package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
- package/lib/rules/prefer-global-router-state-key.js +230 -31
- package/lib/rules/prefer-next-dynamic.d.ts +9 -0
- package/lib/rules/prefer-next-dynamic.js +366 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +430 -0
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +6 -0
- package/lib/rules/prefer-nullish-coalescing-override.js +182 -0
- package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
- package/lib/rules/prefer-params-over-parent-id.js +228 -0
- package/lib/rules/prefer-settings-object.js +23 -15
- package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
- package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
- package/lib/rules/prefer-url-tostring-over-tojson.js +166 -0
- package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
- package/lib/rules/prefer-use-deep-compare-memo.js +428 -0
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +24 -1
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
- package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
- package/lib/rules/react-usememo-should-be-component.js +144 -78
- package/lib/rules/require-dynamic-firebase-imports.js +2 -1
- package/lib/rules/require-image-optimized.js +1 -1
- package/lib/rules/require-memo.js +11 -1
- package/lib/rules/require-usememo-object-literals.js +7 -1
- package/lib/rules/semantic-function-prefixes.js +38 -18
- package/lib/rules/sync-onwrite-name-func.js +5 -1
- package/lib/rules/test-file-location-enforcement.d.ts +1 -0
- package/lib/rules/test-file-location-enforcement.js +57 -0
- package/lib/rules/use-custom-link.js +6 -1
- package/lib/rules/use-custom-router.js +6 -1
- package/lib/rules/use-latest-callback.d.ts +3 -0
- package/lib/rules/use-latest-callback.js +270 -0
- package/lib/utils/ASTHelpers.js +10 -0
- package/lib/utils/createRule.js +1 -1
- package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
- package/lib/utils/ruleTester.d.ts +1 -0
- package/lib/utils/ruleTester.js +4 -1
- package/package.json +21 -14
- package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
|
@@ -7,41 +7,122 @@ exports.enforceFirebaseImports = (0, createRule_1.createRule)({
|
|
|
7
7
|
meta: {
|
|
8
8
|
type: 'problem',
|
|
9
9
|
docs: {
|
|
10
|
-
description: '
|
|
10
|
+
description: 'Require firebaseCloud modules to be loaded via dynamic import so Firebase code stays out of the initial bundle and only loads when needed.',
|
|
11
11
|
recommended: 'error',
|
|
12
12
|
},
|
|
13
|
+
fixable: 'code',
|
|
14
|
+
hasSuggestions: true,
|
|
13
15
|
schema: [],
|
|
14
16
|
messages: {
|
|
15
|
-
noDynamicImport: 'Static
|
|
17
|
+
noDynamicImport: 'Static import from firebaseCloud path "{{importPath}}" eagerly bundles Firebase code into the initial client chunk, which inflates startup time and prevents lazy loading. Replace it with an awaited dynamic import so the code only loads when invoked (e.g., `const module = await import(\'{{importPath}}\')` or destructure the exports you need).',
|
|
16
18
|
},
|
|
17
19
|
},
|
|
18
20
|
defaultOptions: [],
|
|
19
21
|
create(context) {
|
|
20
22
|
return {
|
|
21
23
|
ImportDeclaration(node) {
|
|
22
|
-
// Skip
|
|
24
|
+
// Skip third-party files
|
|
25
|
+
const filename = context.getFilename?.();
|
|
26
|
+
if (filename && /(^|[\\/])node_modules([\\/]|$)/.test(filename)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Skip type-only import declarations
|
|
23
30
|
if (node.importKind === 'type') {
|
|
24
31
|
return;
|
|
25
32
|
}
|
|
26
33
|
const importPath = node.source.value;
|
|
27
34
|
// Check if the import is from firebaseCloud directory
|
|
28
|
-
if (importPath.includes('firebaseCloud/')) {
|
|
29
|
-
|
|
30
|
-
node,
|
|
31
|
-
messageId: 'noDynamicImport',
|
|
32
|
-
fix(fixer) {
|
|
33
|
-
const importSpecifiers = node.specifiers
|
|
34
|
-
.filter((spec) => spec.type === 'ImportSpecifier')
|
|
35
|
-
.map((spec) => spec.imported.name);
|
|
36
|
-
if (importSpecifiers.length === 0) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
const destructuredImports = `{ ${importSpecifiers.join(', ')} }`;
|
|
40
|
-
const dynamicImport = `const ${destructuredImports} = await import('${importPath}');`;
|
|
41
|
-
return fixer.replaceText(node, dynamicImport);
|
|
42
|
-
},
|
|
43
|
-
});
|
|
35
|
+
if (!importPath.includes('firebaseCloud/')) {
|
|
36
|
+
return;
|
|
44
37
|
}
|
|
38
|
+
// Determine specifiers
|
|
39
|
+
const defaultSpecifier = node.specifiers.find((spec) => spec.type === 'ImportDefaultSpecifier');
|
|
40
|
+
const namespaceSpecifier = node.specifiers.find((spec) => spec.type === 'ImportNamespaceSpecifier');
|
|
41
|
+
const namedSpecifiers = node.specifiers.filter((spec) => spec.type === 'ImportSpecifier' && spec.importKind !== 'type');
|
|
42
|
+
const typeOnlySpecifiers = node.specifiers.filter((spec) => spec.type === 'ImportSpecifier' && spec.importKind === 'type');
|
|
43
|
+
// If there are only type-only specifiers, allow
|
|
44
|
+
if (!defaultSpecifier &&
|
|
45
|
+
!namespaceSpecifier &&
|
|
46
|
+
namedSpecifiers.length === 0 &&
|
|
47
|
+
typeOnlySpecifiers.length > 0) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const buildTypeNames = () => typeOnlySpecifiers
|
|
51
|
+
.map((spec) => spec.imported.name === spec.local.name
|
|
52
|
+
? spec.imported.name
|
|
53
|
+
: `${spec.imported.name} as ${spec.local.name}`)
|
|
54
|
+
.join(', ');
|
|
55
|
+
const buildReplacement = (options = {}) => {
|
|
56
|
+
const statements = [];
|
|
57
|
+
if (typeOnlySpecifiers.length > 0) {
|
|
58
|
+
statements.push(`import type { ${buildTypeNames()} } from '${importPath}';`);
|
|
59
|
+
}
|
|
60
|
+
if (namespaceSpecifier) {
|
|
61
|
+
const nsLocal = namespaceSpecifier.local.name;
|
|
62
|
+
statements.push(`const ${nsLocal} = await import('${importPath}');`);
|
|
63
|
+
if (defaultSpecifier) {
|
|
64
|
+
const defLocal = defaultSpecifier.local.name;
|
|
65
|
+
statements.push(`const ${defLocal} = ${nsLocal}.default;`);
|
|
66
|
+
}
|
|
67
|
+
const destructureFromNamespace = [];
|
|
68
|
+
if (namedSpecifiers.length > 0) {
|
|
69
|
+
const destructureParts = namedSpecifiers.map((spec) => {
|
|
70
|
+
const imported = spec.imported.name;
|
|
71
|
+
const local = spec.local.name;
|
|
72
|
+
return imported === local ? imported : `${imported}: ${local}`;
|
|
73
|
+
});
|
|
74
|
+
destructureFromNamespace.push(...destructureParts);
|
|
75
|
+
}
|
|
76
|
+
if (destructureFromNamespace.length > 0) {
|
|
77
|
+
statements.push(`const { ${destructureFromNamespace.join(', ')} } = ${nsLocal};`);
|
|
78
|
+
}
|
|
79
|
+
return statements.join(' ');
|
|
80
|
+
}
|
|
81
|
+
const destructureParts = [];
|
|
82
|
+
if (defaultSpecifier) {
|
|
83
|
+
const defLocal = defaultSpecifier.local.name;
|
|
84
|
+
destructureParts.push(`default: ${defLocal}`);
|
|
85
|
+
}
|
|
86
|
+
if (namedSpecifiers.length > 0) {
|
|
87
|
+
for (const spec of namedSpecifiers) {
|
|
88
|
+
const imported = spec.imported.name;
|
|
89
|
+
const local = spec.local.name;
|
|
90
|
+
destructureParts.push(imported === local ? imported : `${imported}: ${local}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (destructureParts.length > 0) {
|
|
94
|
+
statements.push(`const { ${destructureParts.join(', ')} } = await import('${importPath}');`);
|
|
95
|
+
return statements.join(' ');
|
|
96
|
+
}
|
|
97
|
+
if (node.specifiers.length === 0) {
|
|
98
|
+
return options.allowSideEffectFix !== false
|
|
99
|
+
? `await import('${importPath}');`
|
|
100
|
+
: null;
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
};
|
|
104
|
+
context.report({
|
|
105
|
+
node,
|
|
106
|
+
messageId: 'noDynamicImport',
|
|
107
|
+
data: { importPath },
|
|
108
|
+
fix(fixer) {
|
|
109
|
+
const replacement = buildReplacement();
|
|
110
|
+
return replacement ? fixer.replaceText(node, replacement) : null;
|
|
111
|
+
},
|
|
112
|
+
suggest: [
|
|
113
|
+
{
|
|
114
|
+
messageId: 'noDynamicImport',
|
|
115
|
+
fix(fixer) {
|
|
116
|
+
const replacement = buildReplacement({
|
|
117
|
+
allowSideEffectFix: true,
|
|
118
|
+
});
|
|
119
|
+
return replacement
|
|
120
|
+
? fixer.replaceText(node, replacement)
|
|
121
|
+
: null;
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
});
|
|
45
126
|
},
|
|
46
127
|
};
|
|
47
128
|
},
|
|
@@ -29,7 +29,7 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
29
29
|
},
|
|
30
30
|
],
|
|
31
31
|
messages: {
|
|
32
|
-
dynamicImportRequired: 'Static
|
|
32
|
+
dynamicImportRequired: 'Static import from "{{source}}" eagerly pulls the entire package into the entry bundle, inflating download size and delaying the first render. Load it lazily with a dynamic import (for example, useDynamic(() => import("{{source}}"))) so the code is fetched only when needed; if you only need types, use a type-only import with allowImportType enabled.',
|
|
33
33
|
},
|
|
34
34
|
},
|
|
35
35
|
defaultOptions: [
|
|
@@ -14,9 +14,9 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
14
14
|
},
|
|
15
15
|
schema: [],
|
|
16
16
|
messages: {
|
|
17
|
-
missingExportedType: 'Type {{typeName}}
|
|
18
|
-
missingExportedReturnType: 'Return type {{typeName}}
|
|
19
|
-
missingExportedPropsType: 'Props type {{typeName}}
|
|
17
|
+
missingExportedType: 'Type "{{typeName}}" is used in a parameter of an exported function but is not exported. Callers cannot import the parameter contract, which forces duplicate or ad-hoc types and makes the API drift. Export the type (e.g., `export type {{typeName}} = ...`) or reuse an already exported type.',
|
|
18
|
+
missingExportedReturnType: 'Return type "{{typeName}}" belongs to an exported function but is not exported. Consumers cannot reference the returned shape for validation or composition, which leads to duplicated types and diverging contracts. Export the return type alias or interface so callers can import the shared contract.',
|
|
19
|
+
missingExportedPropsType: 'Props type "{{typeName}}" is used by an exported React component but is not exported. Other modules cannot type the props when composing the component and end up recreating the shape. Export the props type (e.g., `export type {{typeName}} = ...`) or reference an existing exported props contract.',
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
22
|
defaultOptions: [],
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enforceFieldPathSyntaxInDocSetter = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
exports.enforceFieldPathSyntaxInDocSetter = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'enforce-fieldpath-syntax-in-docsetter',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'suggestion',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Enforce the use of Firestore FieldPath syntax when passing documentData into DocSetter. Instead of using nested object syntax, developers should use dot notation for deeply nested fields.',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
requiresTypeChecking: false,
|
|
14
|
+
extendsBaseRule: false,
|
|
15
|
+
},
|
|
16
|
+
fixable: 'code',
|
|
17
|
+
schema: [],
|
|
18
|
+
messages: {
|
|
19
|
+
enforceFieldPathSyntax: 'Use FieldPath syntax (dot notation) for nested fields in DocSetter. Instead of `{ roles: { contributor: value } }`, use `{ "roles.contributor": value }`.',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultOptions: [],
|
|
23
|
+
create(context) {
|
|
24
|
+
// Track DocSetter variables
|
|
25
|
+
const docSetterVariables = new Set();
|
|
26
|
+
// Helper function to check if a node is a DocSetter method call
|
|
27
|
+
function isDocSetterMethodCall(node) {
|
|
28
|
+
if (node.callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const { object, property } = node.callee;
|
|
32
|
+
// Only enforce for set/updateIfExists; skip overwrite (full-document replacement)
|
|
33
|
+
if (property.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
34
|
+
!['set', 'updateIfExists'].includes(property.name)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
// Check if the object is a DocSetter instance
|
|
38
|
+
if (object.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
39
|
+
return docSetterVariables.has(object.name);
|
|
40
|
+
}
|
|
41
|
+
// Support chained instantiation: new DocSetter(...).set(...)
|
|
42
|
+
if (object.type === utils_1.AST_NODE_TYPES.NewExpression &&
|
|
43
|
+
object.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
44
|
+
object.callee.name === 'DocSetter') {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
// Helper: detect spread or computed properties in an object literal
|
|
50
|
+
function isSpreadOrComputed(prop) {
|
|
51
|
+
return (prop.type === utils_1.AST_NODE_TYPES.SpreadElement ||
|
|
52
|
+
(prop.type === utils_1.AST_NODE_TYPES.Property && prop.computed === true));
|
|
53
|
+
}
|
|
54
|
+
// Helper function to check if an object has nested objects (excluding arrays)
|
|
55
|
+
function hasNestedObjects(node, prefix = '') {
|
|
56
|
+
for (const property of node.properties) {
|
|
57
|
+
// Skip spread elements
|
|
58
|
+
if (property.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (property.type !== utils_1.AST_NODE_TYPES.Property) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
// Skip computed properties (dynamic keys)
|
|
65
|
+
if (property.computed) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const isNumericKey = property.key.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
69
|
+
(typeof property.key.value === 'number' ||
|
|
70
|
+
(typeof property.key.value === 'string' &&
|
|
71
|
+
/^\d+$/.test(property.key.value)));
|
|
72
|
+
// Allow top-level numeric keys (treated like array indexes)
|
|
73
|
+
if (prefix === '' && isNumericKey) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const value = property.value;
|
|
77
|
+
// Skip if the property key is already using dot notation
|
|
78
|
+
if (property.key.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
79
|
+
typeof property.key.value === 'string' &&
|
|
80
|
+
property.key.value.includes('.')) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// Check if the value is an object (but not an array)
|
|
84
|
+
if (value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
85
|
+
// Skip nested objects that contain spread elements or computed properties
|
|
86
|
+
const hasSpreadOrComputed = value.properties.some((prop) => isSpreadOrComputed(prop));
|
|
87
|
+
if (!hasSpreadOrComputed) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
// Helper function to flatten nested objects into FieldPath syntax
|
|
95
|
+
function flattenObject(obj, sourceCode, prefix = '') {
|
|
96
|
+
const result = {};
|
|
97
|
+
for (const property of obj.properties) {
|
|
98
|
+
// Skip spread elements
|
|
99
|
+
if (property.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (property.type !== utils_1.AST_NODE_TYPES.Property) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
// Skip computed properties (dynamic keys)
|
|
106
|
+
if (property.computed) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
let key;
|
|
110
|
+
if (property.key.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
111
|
+
key = property.key.name;
|
|
112
|
+
}
|
|
113
|
+
else if (property.key.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
114
|
+
// Handle both string and numeric literal keys
|
|
115
|
+
if (typeof property.key.value === 'string') {
|
|
116
|
+
key = property.key.value;
|
|
117
|
+
}
|
|
118
|
+
else if (typeof property.key.value === 'number') {
|
|
119
|
+
key = String(property.key.value);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// Skip other literal types
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// Skip other key types
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const isNumericKey = property.key.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
131
|
+
(typeof property.key.value === 'number' ||
|
|
132
|
+
(typeof property.key.value === 'string' &&
|
|
133
|
+
/^\d+$/.test(property.key.value)));
|
|
134
|
+
if (prefix === '' && isNumericKey) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
138
|
+
// If the value is a nested object, recursively flatten it
|
|
139
|
+
if (property.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
140
|
+
const nestedResult = flattenObject(property.value, sourceCode, fullKey);
|
|
141
|
+
Object.assign(result, nestedResult);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// For non-object values, use the key as is
|
|
145
|
+
result[fullKey] = sourceCode.getText(property.value);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
// Helper to decide if a key needs quoting (contains dot or is not IdentifierName)
|
|
151
|
+
function needsQuoting(key) {
|
|
152
|
+
return key.includes('.') || !/^(?:[$_A-Za-z][$\w]*)$/u.test(key);
|
|
153
|
+
}
|
|
154
|
+
// Helper function to convert an object to FieldPath syntax
|
|
155
|
+
function convertToFieldPathSyntax(node, sourceCode) {
|
|
156
|
+
const idProperty = node.properties.find((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
157
|
+
prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
158
|
+
prop.key.name === 'id');
|
|
159
|
+
const flattenedProperties = flattenObject(node, sourceCode);
|
|
160
|
+
if (idProperty && idProperty.type === utils_1.AST_NODE_TYPES.Property) {
|
|
161
|
+
delete flattenedProperties['id'];
|
|
162
|
+
}
|
|
163
|
+
const entries = Object.entries(flattenedProperties);
|
|
164
|
+
const propertyComma = ',';
|
|
165
|
+
// Start with the id property if it exists
|
|
166
|
+
let result = '{\n';
|
|
167
|
+
if (idProperty && idProperty.type === utils_1.AST_NODE_TYPES.Property) {
|
|
168
|
+
result += ` id: ${sourceCode.getText(idProperty.value)}${propertyComma}\n`;
|
|
169
|
+
}
|
|
170
|
+
// Add the flattened properties (always include trailing commas for multiline objects)
|
|
171
|
+
entries.forEach(([key, value]) => {
|
|
172
|
+
const printedKey = needsQuoting(key) ? `'${key}'` : key;
|
|
173
|
+
result += ` ${printedKey}: ${value}${propertyComma}\n`;
|
|
174
|
+
});
|
|
175
|
+
result += '}';
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
// Track DocSetter variable declarations
|
|
180
|
+
VariableDeclarator(node) {
|
|
181
|
+
if (node.init?.type === utils_1.AST_NODE_TYPES.NewExpression &&
|
|
182
|
+
node.init.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
183
|
+
node.init.callee.name === 'DocSetter' &&
|
|
184
|
+
node.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
185
|
+
docSetterVariables.add(node.id.name);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
// Check DocSetter method calls
|
|
189
|
+
CallExpression(node) {
|
|
190
|
+
if (!isDocSetterMethodCall(node)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
// Check if the first argument is an object literal
|
|
194
|
+
const firstArg = node.arguments[0];
|
|
195
|
+
if (firstArg?.type !== utils_1.AST_NODE_TYPES.ObjectExpression ||
|
|
196
|
+
!hasNestedObjects(firstArg)) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
// Report and fix the issue
|
|
200
|
+
context.report({
|
|
201
|
+
node: firstArg,
|
|
202
|
+
messageId: 'enforceFieldPathSyntax',
|
|
203
|
+
fix(fixer) {
|
|
204
|
+
const newText = convertToFieldPathSyntax(firstArg, context.getSourceCode());
|
|
205
|
+
return fixer.replaceText(firstArg, newText);
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
//# sourceMappingURL=enforce-fieldpath-syntax-in-docsetter.js.map
|