@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.
Files changed (141) hide show
  1. package/README.md +140 -99
  2. package/lib/index.js +111 -17
  3. package/lib/rules/array-methods-this-context.js +20 -2
  4. package/lib/rules/avoid-utils-directory.js +11 -2
  5. package/lib/rules/dynamic-https-errors.d.ts +3 -1
  6. package/lib/rules/dynamic-https-errors.js +48 -10
  7. package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
  8. package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +51 -95
  9. package/lib/rules/enforce-assert-throws.d.ts +3 -1
  10. package/lib/rules/enforce-assert-throws.js +493 -51
  11. package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
  12. package/lib/rules/enforce-boolean-naming-prefixes.js +832 -39
  13. package/lib/rules/enforce-callable-types.js +4 -4
  14. package/lib/rules/enforce-callback-memo.js +143 -18
  15. package/lib/rules/enforce-centralized-mock-firestore.js +4 -1
  16. package/lib/rules/enforce-console-error.d.ts +3 -0
  17. package/lib/rules/enforce-console-error.js +250 -0
  18. package/lib/rules/enforce-css-media-queries.js +24 -46
  19. package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
  20. package/lib/rules/enforce-dynamic-imports.js +1 -1
  21. package/lib/rules/enforce-exported-function-types.js +3 -3
  22. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
  23. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +212 -0
  24. package/lib/rules/enforce-firestore-doc-ref-generic.js +335 -0
  25. package/lib/rules/enforce-firestore-facade.js +347 -157
  26. package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
  27. package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
  28. package/lib/rules/enforce-global-constants.d.ts +4 -1
  29. package/lib/rules/enforce-global-constants.js +240 -12
  30. package/lib/rules/enforce-memoize-async.d.ts +2 -1
  31. package/lib/rules/enforce-memoize-async.js +96 -27
  32. package/lib/rules/enforce-memoize-getters.d.ts +3 -0
  33. package/lib/rules/enforce-memoize-getters.js +123 -0
  34. package/lib/rules/enforce-microdiff.js +2 -1
  35. package/lib/rules/enforce-positive-naming.js +5 -7
  36. package/lib/rules/enforce-props-argument-name.d.ts +2 -7
  37. package/lib/rules/enforce-props-argument-name.js +165 -151
  38. package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
  39. package/lib/rules/enforce-props-naming-consistency.js +145 -0
  40. package/lib/rules/enforce-querykey-ts.d.ts +9 -0
  41. package/lib/rules/enforce-querykey-ts.js +274 -0
  42. package/lib/rules/enforce-singular-type-names.js +9 -5
  43. package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
  44. package/lib/rules/enforce-typescript-markdown-code-blocks.js +96 -0
  45. package/lib/rules/enforce-verb-noun-naming.js +85 -10
  46. package/lib/rules/extract-global-constants.js +57 -45
  47. package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
  48. package/lib/rules/fast-deep-equal-over-microdiff.js +356 -78
  49. package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
  50. package/lib/rules/firestore-transaction-reads-before-writes.js +231 -0
  51. package/lib/rules/generic-starts-with-t.js +8 -2
  52. package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
  53. package/lib/rules/memoize-root-level-hocs.js +242 -0
  54. package/lib/rules/no-array-length-in-deps.d.ts +12 -0
  55. package/lib/rules/no-array-length-in-deps.js +308 -0
  56. package/lib/rules/no-async-array-filter.js +5 -2
  57. package/lib/rules/no-complex-cloud-params.js +15 -2
  58. package/lib/rules/no-compositing-layer-props.js +4 -2
  59. package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
  60. package/lib/rules/no-entire-object-hook-deps.js +317 -63
  61. package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
  62. package/lib/rules/no-excessive-parent-chain.js +273 -0
  63. package/lib/rules/no-firestore-object-arrays.js +345 -30
  64. package/lib/rules/no-hungarian.js +13 -13
  65. package/lib/rules/no-margin-properties.d.ts +7 -1
  66. package/lib/rules/no-margin-properties.js +16 -3
  67. package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
  68. package/lib/rules/no-misleading-boolean-prefixes.js +327 -0
  69. package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
  70. package/lib/rules/no-overridable-method-calls-in-constructor.js +260 -0
  71. package/lib/rules/no-passthrough-getters.d.ts +4 -0
  72. package/lib/rules/no-passthrough-getters.js +170 -0
  73. package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
  74. package/lib/rules/no-redundant-usecallback-wrapper.js +315 -0
  75. package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
  76. package/lib/rules/no-restricted-properties-fix.js +172 -0
  77. package/lib/rules/no-separate-loading-state.d.ts +8 -0
  78. package/lib/rules/no-separate-loading-state.js +142 -0
  79. package/lib/rules/no-stale-state-across-await.d.ts +1 -0
  80. package/lib/rules/no-stale-state-across-await.js +185 -0
  81. package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
  82. package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
  83. package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
  84. package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
  85. package/lib/rules/no-type-assertion-returns.js +6 -0
  86. package/lib/rules/no-undefined-null-passthrough.js +166 -19
  87. package/lib/rules/no-unnecessary-verb-suffix.js +0 -2
  88. package/lib/rules/no-unsafe-firestore-spread.js +8 -5
  89. package/lib/rules/no-unused-props.d.ts +3 -0
  90. package/lib/rules/no-unused-props.js +213 -78
  91. package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
  92. package/lib/rules/optimize-object-boolean-conditions.js +234 -0
  93. package/lib/rules/parallelize-async-operations.d.ts +8 -0
  94. package/lib/rules/parallelize-async-operations.js +375 -0
  95. package/lib/rules/prefer-batch-operations.js +199 -33
  96. package/lib/rules/prefer-block-comments-for-declarations.js +13 -3
  97. package/lib/rules/prefer-document-flattening.d.ts +4 -0
  98. package/lib/rules/prefer-document-flattening.js +239 -0
  99. package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
  100. package/lib/rules/prefer-field-paths-in-transforms.js +250 -0
  101. package/lib/rules/prefer-fragment-component.js +3 -3
  102. package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
  103. package/lib/rules/prefer-global-router-state-key.js +230 -31
  104. package/lib/rules/prefer-next-dynamic.d.ts +9 -0
  105. package/lib/rules/prefer-next-dynamic.js +366 -0
  106. package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
  107. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +430 -0
  108. package/lib/rules/prefer-nullish-coalescing-override.d.ts +6 -0
  109. package/lib/rules/prefer-nullish-coalescing-override.js +182 -0
  110. package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
  111. package/lib/rules/prefer-params-over-parent-id.js +228 -0
  112. package/lib/rules/prefer-settings-object.js +23 -15
  113. package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
  114. package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
  115. package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
  116. package/lib/rules/prefer-url-tostring-over-tojson.js +166 -0
  117. package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
  118. package/lib/rules/prefer-use-deep-compare-memo.js +428 -0
  119. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +24 -1
  120. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
  121. package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
  122. package/lib/rules/react-usememo-should-be-component.js +144 -78
  123. package/lib/rules/require-dynamic-firebase-imports.js +2 -1
  124. package/lib/rules/require-image-optimized.js +1 -1
  125. package/lib/rules/require-memo.js +11 -1
  126. package/lib/rules/require-usememo-object-literals.js +7 -1
  127. package/lib/rules/semantic-function-prefixes.js +38 -18
  128. package/lib/rules/sync-onwrite-name-func.js +5 -1
  129. package/lib/rules/test-file-location-enforcement.d.ts +1 -0
  130. package/lib/rules/test-file-location-enforcement.js +57 -0
  131. package/lib/rules/use-custom-link.js +6 -1
  132. package/lib/rules/use-custom-router.js +6 -1
  133. package/lib/rules/use-latest-callback.d.ts +3 -0
  134. package/lib/rules/use-latest-callback.js +270 -0
  135. package/lib/utils/ASTHelpers.js +10 -0
  136. package/lib/utils/createRule.js +1 -1
  137. package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
  138. package/lib/utils/ruleTester.d.ts +1 -0
  139. package/lib/utils/ruleTester.js +4 -1
  140. package/package.json +21 -14
  141. 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: 'Enforce dynamic importing for modules within the firebaseCloud directory to optimize initial bundle size. This ensures Firebase-related code is only loaded when needed, improving application startup time and reducing the main bundle size.',
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 imports from firebaseCloud directory are not allowed to reduce initial bundle size. Instead of `import { func } from "./firebaseCloud/module"`, use dynamic import: `const { func } = await import("./firebaseCloud/module")`.',
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 type-only imports
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
- context.report({
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 imports from "{{source}}" are not allowed to reduce initial bundle size. Use dynamic imports instead: const Component = useDynamic(() => import("{{source}}").then(mod => mod.Component))',
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}} should be exported since it is used in an exported function. Add `export` before the type definition: `export type {{typeName}} = ...`',
18
- missingExportedReturnType: 'Return type {{typeName}} should be exported since it is used in an exported function. Add `export` before the type definition: `export type {{typeName}} = ...`',
19
- missingExportedPropsType: 'Props type {{typeName}} should be exported since it is used in an exported React component. Add `export` before the type definition: `export 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,2 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ export declare const enforceFieldPathSyntaxInDocSetter: TSESLint.RuleModule<"enforceFieldPathSyntax", [], TSESLint.RuleListener>;
@@ -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