@memberjunction/react-linter 0.0.1 → 5.38.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/dist/component-linter.d.ts +77 -0
- package/dist/component-linter.d.ts.map +1 -0
- package/dist/component-linter.js +1206 -0
- package/dist/component-linter.js.map +1 -0
- package/dist/control-flow-analyzer.d.ts +184 -0
- package/dist/control-flow-analyzer.d.ts.map +1 -0
- package/dist/control-flow-analyzer.js +798 -0
- package/dist/control-flow-analyzer.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/library-lint-cache.d.ts +50 -0
- package/dist/library-lint-cache.d.ts.map +1 -0
- package/dist/library-lint-cache.js +173 -0
- package/dist/library-lint-cache.js.map +1 -0
- package/dist/lint-rule.d.ts +70 -0
- package/dist/lint-rule.d.ts.map +1 -0
- package/dist/lint-rule.js +30 -0
- package/dist/lint-rule.js.map +1 -0
- package/dist/lint-utils.d.ts +131 -0
- package/dist/lint-utils.d.ts.map +1 -0
- package/dist/lint-utils.js +358 -0
- package/dist/lint-utils.js.map +1 -0
- package/dist/linter-options.d.ts +51 -0
- package/dist/linter-options.d.ts.map +1 -0
- package/dist/linter-options.js +2 -0
- package/dist/linter-options.js.map +1 -0
- package/dist/prop-value-extractor.d.ts +147 -0
- package/dist/prop-value-extractor.d.ts.map +1 -0
- package/dist/prop-value-extractor.js +472 -0
- package/dist/prop-value-extractor.js.map +1 -0
- package/dist/runtime-rules/ai-tools-availability-check.d.ts +9 -0
- package/dist/runtime-rules/ai-tools-availability-check.d.ts.map +1 -0
- package/dist/runtime-rules/ai-tools-availability-check.js +223 -0
- package/dist/runtime-rules/ai-tools-availability-check.js.map +1 -0
- package/dist/runtime-rules/callback-event-validation.d.ts +22 -0
- package/dist/runtime-rules/callback-event-validation.d.ts.map +1 -0
- package/dist/runtime-rules/callback-event-validation.js +561 -0
- package/dist/runtime-rules/callback-event-validation.js.map +1 -0
- package/dist/runtime-rules/chart-field-validation.d.ts +10 -0
- package/dist/runtime-rules/chart-field-validation.d.ts.map +1 -0
- package/dist/runtime-rules/chart-field-validation.js +270 -0
- package/dist/runtime-rules/chart-field-validation.js.map +1 -0
- package/dist/runtime-rules/child-component-prop-validation.d.ts +11 -0
- package/dist/runtime-rules/child-component-prop-validation.d.ts.map +1 -0
- package/dist/runtime-rules/child-component-prop-validation.js +443 -0
- package/dist/runtime-rules/child-component-prop-validation.js.map +1 -0
- package/dist/runtime-rules/component-name-mismatch.d.ts +19 -0
- package/dist/runtime-rules/component-name-mismatch.d.ts.map +1 -0
- package/dist/runtime-rules/component-name-mismatch.js +82 -0
- package/dist/runtime-rules/component-name-mismatch.js.map +1 -0
- package/dist/runtime-rules/component-not-in-dependencies.d.ts +20 -0
- package/dist/runtime-rules/component-not-in-dependencies.d.ts.map +1 -0
- package/dist/runtime-rules/component-not-in-dependencies.js +92 -0
- package/dist/runtime-rules/component-not-in-dependencies.js.map +1 -0
- package/dist/runtime-rules/component-props-validation.d.ts +25 -0
- package/dist/runtime-rules/component-props-validation.d.ts.map +1 -0
- package/dist/runtime-rules/component-props-validation.js +228 -0
- package/dist/runtime-rules/component-props-validation.js.map +1 -0
- package/dist/runtime-rules/component-usage-without-destructuring.d.ts +20 -0
- package/dist/runtime-rules/component-usage-without-destructuring.d.ts.map +1 -0
- package/dist/runtime-rules/component-usage-without-destructuring.js +124 -0
- package/dist/runtime-rules/component-usage-without-destructuring.js.map +1 -0
- package/dist/runtime-rules/data-result-validation.d.ts +9 -0
- package/dist/runtime-rules/data-result-validation.d.ts.map +1 -0
- package/dist/runtime-rules/data-result-validation.js +763 -0
- package/dist/runtime-rules/data-result-validation.js.map +1 -0
- package/dist/runtime-rules/datagrid-field-validation.d.ts +10 -0
- package/dist/runtime-rules/datagrid-field-validation.d.ts.map +1 -0
- package/dist/runtime-rules/datagrid-field-validation.js +249 -0
- package/dist/runtime-rules/datagrid-field-validation.js.map +1 -0
- package/dist/runtime-rules/dependency-shadowing.d.ts +20 -0
- package/dist/runtime-rules/dependency-shadowing.d.ts.map +1 -0
- package/dist/runtime-rules/dependency-shadowing.js +147 -0
- package/dist/runtime-rules/dependency-shadowing.js.map +1 -0
- package/dist/runtime-rules/entity-field-access-validation.d.ts +12 -0
- package/dist/runtime-rules/entity-field-access-validation.d.ts.map +1 -0
- package/dist/runtime-rules/entity-field-access-validation.js +304 -0
- package/dist/runtime-rules/entity-field-access-validation.js.map +1 -0
- package/dist/runtime-rules/event-parameter-validation.d.ts +22 -0
- package/dist/runtime-rules/event-parameter-validation.d.ts.map +1 -0
- package/dist/runtime-rules/event-parameter-validation.js +406 -0
- package/dist/runtime-rules/event-parameter-validation.js.map +1 -0
- package/dist/runtime-rules/index.d.ts +61 -0
- package/dist/runtime-rules/index.d.ts.map +1 -0
- package/dist/runtime-rules/index.js +62 -0
- package/dist/runtime-rules/index.js.map +1 -0
- package/dist/runtime-rules/library-variable-names.d.ts +24 -0
- package/dist/runtime-rules/library-variable-names.d.ts.map +1 -0
- package/dist/runtime-rules/library-variable-names.js +88 -0
- package/dist/runtime-rules/library-variable-names.js.map +1 -0
- package/dist/runtime-rules/no-child-implementation.d.ts +18 -0
- package/dist/runtime-rules/no-child-implementation.d.ts.map +1 -0
- package/dist/runtime-rules/no-child-implementation.js +57 -0
- package/dist/runtime-rules/no-child-implementation.js.map +1 -0
- package/dist/runtime-rules/no-data-prop.d.ts +22 -0
- package/dist/runtime-rules/no-data-prop.d.ts.map +1 -0
- package/dist/runtime-rules/no-data-prop.js +111 -0
- package/dist/runtime-rules/no-data-prop.js.map +1 -0
- package/dist/runtime-rules/no-export-statements.d.ts +18 -0
- package/dist/runtime-rules/no-export-statements.d.ts.map +1 -0
- package/dist/runtime-rules/no-export-statements.js +143 -0
- package/dist/runtime-rules/no-export-statements.js.map +1 -0
- package/dist/runtime-rules/no-iife-wrapper.d.ts +18 -0
- package/dist/runtime-rules/no-iife-wrapper.d.ts.map +1 -0
- package/dist/runtime-rules/no-iife-wrapper.js +217 -0
- package/dist/runtime-rules/no-iife-wrapper.js.map +1 -0
- package/dist/runtime-rules/no-import-statements.d.ts +18 -0
- package/dist/runtime-rules/no-import-statements.d.ts.map +1 -0
- package/dist/runtime-rules/no-import-statements.js +65 -0
- package/dist/runtime-rules/no-import-statements.js.map +1 -0
- package/dist/runtime-rules/no-react-destructuring.d.ts +18 -0
- package/dist/runtime-rules/no-react-destructuring.d.ts.map +1 -0
- package/dist/runtime-rules/no-react-destructuring.js +60 -0
- package/dist/runtime-rules/no-react-destructuring.js.map +1 -0
- package/dist/runtime-rules/no-require-statements.d.ts +18 -0
- package/dist/runtime-rules/no-require-statements.d.ts.map +1 -0
- package/dist/runtime-rules/no-require-statements.js +109 -0
- package/dist/runtime-rules/no-require-statements.js.map +1 -0
- package/dist/runtime-rules/no-return-component.d.ts +18 -0
- package/dist/runtime-rules/no-return-component.d.ts.map +1 -0
- package/dist/runtime-rules/no-return-component.js +106 -0
- package/dist/runtime-rules/no-return-component.js.map +1 -0
- package/dist/runtime-rules/no-unwrap-utility-libs.d.ts +20 -0
- package/dist/runtime-rules/no-unwrap-utility-libs.d.ts.map +1 -0
- package/dist/runtime-rules/no-unwrap-utility-libs.js +75 -0
- package/dist/runtime-rules/no-unwrap-utility-libs.js.map +1 -0
- package/dist/runtime-rules/no-use-reducer.d.ts +19 -0
- package/dist/runtime-rules/no-use-reducer.d.ts.map +1 -0
- package/dist/runtime-rules/no-use-reducer.js +78 -0
- package/dist/runtime-rules/no-use-reducer.js.map +1 -0
- package/dist/runtime-rules/no-window-access.d.ts +23 -0
- package/dist/runtime-rules/no-window-access.d.ts.map +1 -0
- package/dist/runtime-rules/no-window-access.js +136 -0
- package/dist/runtime-rules/no-window-access.js.map +1 -0
- package/dist/runtime-rules/noisy-settings-updates.d.ts +18 -0
- package/dist/runtime-rules/noisy-settings-updates.d.ts.map +1 -0
- package/dist/runtime-rules/noisy-settings-updates.js +110 -0
- package/dist/runtime-rules/noisy-settings-updates.js.map +1 -0
- package/dist/runtime-rules/overflow-hidden-on-layout-container.d.ts +30 -0
- package/dist/runtime-rules/overflow-hidden-on-layout-container.d.ts.map +1 -0
- package/dist/runtime-rules/overflow-hidden-on-layout-container.js +220 -0
- package/dist/runtime-rules/overflow-hidden-on-layout-container.js.map +1 -0
- package/dist/runtime-rules/pass-standard-props.d.ts +19 -0
- package/dist/runtime-rules/pass-standard-props.d.ts.map +1 -0
- package/dist/runtime-rules/pass-standard-props.js +82 -0
- package/dist/runtime-rules/pass-standard-props.js.map +1 -0
- package/dist/runtime-rules/prefer-async-await.d.ts +17 -0
- package/dist/runtime-rules/prefer-async-await.d.ts.map +1 -0
- package/dist/runtime-rules/prefer-async-await.js +52 -0
- package/dist/runtime-rules/prefer-async-await.js.map +1 -0
- package/dist/runtime-rules/prefer-jsx-syntax.d.ts +17 -0
- package/dist/runtime-rules/prefer-jsx-syntax.d.ts.map +1 -0
- package/dist/runtime-rules/prefer-jsx-syntax.js +51 -0
- package/dist/runtime-rules/prefer-jsx-syntax.js.map +1 -0
- package/dist/runtime-rules/prop-state-sync.d.ts +19 -0
- package/dist/runtime-rules/prop-state-sync.d.ts.map +1 -0
- package/dist/runtime-rules/prop-state-sync.js +76 -0
- package/dist/runtime-rules/prop-state-sync.js.map +1 -0
- package/dist/runtime-rules/property-name-consistency.d.ts +20 -0
- package/dist/runtime-rules/property-name-consistency.d.ts.map +1 -0
- package/dist/runtime-rules/property-name-consistency.js +172 -0
- package/dist/runtime-rules/property-name-consistency.js.map +1 -0
- package/dist/runtime-rules/query-result-field-access-validation.d.ts +10 -0
- package/dist/runtime-rules/query-result-field-access-validation.d.ts.map +1 -0
- package/dist/runtime-rules/query-result-field-access-validation.js +304 -0
- package/dist/runtime-rules/query-result-field-access-validation.js.map +1 -0
- package/dist/runtime-rules/react-component-naming.d.ts +19 -0
- package/dist/runtime-rules/react-component-naming.d.ts.map +1 -0
- package/dist/runtime-rules/react-component-naming.js +72 -0
- package/dist/runtime-rules/react-component-naming.js.map +1 -0
- package/dist/runtime-rules/react-hooks-rules.d.ts +27 -0
- package/dist/runtime-rules/react-hooks-rules.d.ts.map +1 -0
- package/dist/runtime-rules/react-hooks-rules.js +223 -0
- package/dist/runtime-rules/react-hooks-rules.js.map +1 -0
- package/dist/runtime-rules/required-queries-not-called.d.ts +19 -0
- package/dist/runtime-rules/required-queries-not-called.d.ts.map +1 -0
- package/dist/runtime-rules/required-queries-not-called.js +146 -0
- package/dist/runtime-rules/required-queries-not-called.js.map +1 -0
- package/dist/runtime-rules/runquery-call-validation.d.ts +11 -0
- package/dist/runtime-rules/runquery-call-validation.d.ts.map +1 -0
- package/dist/runtime-rules/runquery-call-validation.js +886 -0
- package/dist/runtime-rules/runquery-call-validation.js.map +1 -0
- package/dist/runtime-rules/runview-call-validation.d.ts +10 -0
- package/dist/runtime-rules/runview-call-validation.d.ts.map +1 -0
- package/dist/runtime-rules/runview-call-validation.js +336 -0
- package/dist/runtime-rules/runview-call-validation.js.map +1 -0
- package/dist/runtime-rules/saved-user-settings-pattern.d.ts +19 -0
- package/dist/runtime-rules/saved-user-settings-pattern.d.ts.map +1 -0
- package/dist/runtime-rules/saved-user-settings-pattern.js +90 -0
- package/dist/runtime-rules/saved-user-settings-pattern.js.map +1 -0
- package/dist/runtime-rules/search-availability-check.d.ts +9 -0
- package/dist/runtime-rules/search-availability-check.d.ts.map +1 -0
- package/dist/runtime-rules/search-availability-check.js +220 -0
- package/dist/runtime-rules/search-availability-check.js.map +1 -0
- package/dist/runtime-rules/search-call-validation.d.ts +9 -0
- package/dist/runtime-rules/search-call-validation.d.ts.map +1 -0
- package/dist/runtime-rules/search-call-validation.js +336 -0
- package/dist/runtime-rules/search-call-validation.js.map +1 -0
- package/dist/runtime-rules/server-reload-on-client-operation.d.ts +18 -0
- package/dist/runtime-rules/server-reload-on-client-operation.d.ts.map +1 -0
- package/dist/runtime-rules/server-reload-on-client-operation.js +107 -0
- package/dist/runtime-rules/server-reload-on-client-operation.js.map +1 -0
- package/dist/runtime-rules/single-function-only.d.ts +18 -0
- package/dist/runtime-rules/single-function-only.d.ts.map +1 -0
- package/dist/runtime-rules/single-function-only.js +103 -0
- package/dist/runtime-rules/single-function-only.js.map +1 -0
- package/dist/runtime-rules/string-replace-all-occurrences.d.ts +19 -0
- package/dist/runtime-rules/string-replace-all-occurrences.d.ts.map +1 -0
- package/dist/runtime-rules/string-replace-all-occurrences.js +109 -0
- package/dist/runtime-rules/string-replace-all-occurrences.js.map +1 -0
- package/dist/runtime-rules/string-template-validation.d.ts +22 -0
- package/dist/runtime-rules/string-template-validation.d.ts.map +1 -0
- package/dist/runtime-rules/string-template-validation.js +163 -0
- package/dist/runtime-rules/string-template-validation.js.map +1 -0
- package/dist/runtime-rules/styles-validation.d.ts +10 -0
- package/dist/runtime-rules/styles-validation.d.ts.map +1 -0
- package/dist/runtime-rules/styles-validation.js +153 -0
- package/dist/runtime-rules/styles-validation.js.map +1 -0
- package/dist/runtime-rules/type-inference-errors.d.ts +23 -0
- package/dist/runtime-rules/type-inference-errors.d.ts.map +1 -0
- package/dist/runtime-rules/type-inference-errors.js +53 -0
- package/dist/runtime-rules/type-inference-errors.js.map +1 -0
- package/dist/runtime-rules/type-mismatch-operation.d.ts +23 -0
- package/dist/runtime-rules/type-mismatch-operation.d.ts.map +1 -0
- package/dist/runtime-rules/type-mismatch-operation.js +145 -0
- package/dist/runtime-rules/type-mismatch-operation.js.map +1 -0
- package/dist/runtime-rules/undefined-component-usage.d.ts +20 -0
- package/dist/runtime-rules/undefined-component-usage.d.ts.map +1 -0
- package/dist/runtime-rules/undefined-component-usage.js +138 -0
- package/dist/runtime-rules/undefined-component-usage.js.map +1 -0
- package/dist/runtime-rules/undefined-jsx-component.d.ts +25 -0
- package/dist/runtime-rules/undefined-jsx-component.d.ts.map +1 -0
- package/dist/runtime-rules/undefined-jsx-component.js +269 -0
- package/dist/runtime-rules/undefined-jsx-component.js.map +1 -0
- package/dist/runtime-rules/unsafe-array-operations.d.ts +25 -0
- package/dist/runtime-rules/unsafe-array-operations.d.ts.map +1 -0
- package/dist/runtime-rules/unsafe-array-operations.js +347 -0
- package/dist/runtime-rules/unsafe-array-operations.js.map +1 -0
- package/dist/runtime-rules/unsafe-formatting-methods.d.ts +24 -0
- package/dist/runtime-rules/unsafe-formatting-methods.d.ts.map +1 -0
- package/dist/runtime-rules/unsafe-formatting-methods.js +277 -0
- package/dist/runtime-rules/unsafe-formatting-methods.js.map +1 -0
- package/dist/runtime-rules/unused-component-dependencies.d.ts +19 -0
- package/dist/runtime-rules/unused-component-dependencies.d.ts.map +1 -0
- package/dist/runtime-rules/unused-component-dependencies.js +90 -0
- package/dist/runtime-rules/unused-component-dependencies.js.map +1 -0
- package/dist/runtime-rules/unused-libraries.d.ts +19 -0
- package/dist/runtime-rules/unused-libraries.d.ts.map +1 -0
- package/dist/runtime-rules/unused-libraries.js +127 -0
- package/dist/runtime-rules/unused-libraries.js.map +1 -0
- package/dist/runtime-rules/use-function-declaration.d.ts +18 -0
- package/dist/runtime-rules/use-function-declaration.d.ts.map +1 -0
- package/dist/runtime-rules/use-function-declaration.js +127 -0
- package/dist/runtime-rules/use-function-declaration.js.map +1 -0
- package/dist/runtime-rules/use-unwrap-components.d.ts +19 -0
- package/dist/runtime-rules/use-unwrap-components.d.ts.map +1 -0
- package/dist/runtime-rules/use-unwrap-components.js +84 -0
- package/dist/runtime-rules/use-unwrap-components.js.map +1 -0
- package/dist/runtime-rules/useeffect-unstable-dependencies.d.ts +23 -0
- package/dist/runtime-rules/useeffect-unstable-dependencies.d.ts.map +1 -0
- package/dist/runtime-rules/useeffect-unstable-dependencies.js +215 -0
- package/dist/runtime-rules/useeffect-unstable-dependencies.js.map +1 -0
- package/dist/runtime-rules/utilities-api-validation.d.ts +24 -0
- package/dist/runtime-rules/utilities-api-validation.d.ts.map +1 -0
- package/dist/runtime-rules/utilities-api-validation.js +121 -0
- package/dist/runtime-rules/utilities-api-validation.js.map +1 -0
- package/dist/runtime-rules/utilities-no-direct-instantiation.d.ts +20 -0
- package/dist/runtime-rules/utilities-no-direct-instantiation.d.ts.map +1 -0
- package/dist/runtime-rules/utilities-no-direct-instantiation.js +58 -0
- package/dist/runtime-rules/utilities-no-direct-instantiation.js.map +1 -0
- package/dist/runtime-rules/validate-component-references.d.ts +19 -0
- package/dist/runtime-rules/validate-component-references.d.ts.map +1 -0
- package/dist/runtime-rules/validate-component-references.js +255 -0
- package/dist/runtime-rules/validate-component-references.js.map +1 -0
- package/dist/schema-validation/component-prop-rule.d.ts +131 -0
- package/dist/schema-validation/component-prop-rule.d.ts.map +1 -0
- package/dist/schema-validation/component-prop-rule.js +625 -0
- package/dist/schema-validation/component-prop-rule.js.map +1 -0
- package/dist/schema-validation/index.d.ts +26 -0
- package/dist/schema-validation/index.d.ts.map +1 -0
- package/dist/schema-validation/index.js +26 -0
- package/dist/schema-validation/index.js.map +1 -0
- package/dist/schema-validation/semantic-validators/index.d.ts +23 -0
- package/dist/schema-validation/semantic-validators/index.d.ts.map +1 -0
- package/dist/schema-validation/semantic-validators/index.js +23 -0
- package/dist/schema-validation/semantic-validators/index.js.map +1 -0
- package/dist/schema-validation/semantic-validators/required-when-validator.d.ts +43 -0
- package/dist/schema-validation/semantic-validators/required-when-validator.d.ts.map +1 -0
- package/dist/schema-validation/semantic-validators/required-when-validator.js +94 -0
- package/dist/schema-validation/semantic-validators/required-when-validator.js.map +1 -0
- package/dist/schema-validation/semantic-validators/semantic-validator-registry.d.ts +122 -0
- package/dist/schema-validation/semantic-validators/semantic-validator-registry.d.ts.map +1 -0
- package/dist/schema-validation/semantic-validators/semantic-validator-registry.js +166 -0
- package/dist/schema-validation/semantic-validators/semantic-validator-registry.js.map +1 -0
- package/dist/schema-validation/semantic-validators/semantic-validator.d.ts +260 -0
- package/dist/schema-validation/semantic-validators/semantic-validator.d.ts.map +1 -0
- package/dist/schema-validation/semantic-validators/semantic-validator.js +301 -0
- package/dist/schema-validation/semantic-validators/semantic-validator.js.map +1 -0
- package/dist/schema-validation/semantic-validators/sql-where-clause-validator.d.ts +115 -0
- package/dist/schema-validation/semantic-validators/sql-where-clause-validator.d.ts.map +1 -0
- package/dist/schema-validation/semantic-validators/sql-where-clause-validator.js +381 -0
- package/dist/schema-validation/semantic-validators/sql-where-clause-validator.js.map +1 -0
- package/dist/schema-validation/semantic-validators/subset-of-entity-fields-validator.d.ts +60 -0
- package/dist/schema-validation/semantic-validators/subset-of-entity-fields-validator.d.ts.map +1 -0
- package/dist/schema-validation/semantic-validators/subset-of-entity-fields-validator.js +195 -0
- package/dist/schema-validation/semantic-validators/subset-of-entity-fields-validator.js.map +1 -0
- package/dist/schema-validation/semantic-validators/validation-context.d.ts +335 -0
- package/dist/schema-validation/semantic-validators/validation-context.d.ts.map +1 -0
- package/dist/schema-validation/semantic-validators/validation-context.js +13 -0
- package/dist/schema-validation/semantic-validators/validation-context.js.map +1 -0
- package/dist/styles-type-analyzer.d.ts +64 -0
- package/dist/styles-type-analyzer.d.ts.map +1 -0
- package/dist/styles-type-analyzer.js +242 -0
- package/dist/styles-type-analyzer.js.map +1 -0
- package/dist/type-context.d.ts +184 -0
- package/dist/type-context.d.ts.map +1 -0
- package/dist/type-context.js +415 -0
- package/dist/type-context.js.map +1 -0
- package/dist/type-inference-engine.d.ts +181 -0
- package/dist/type-inference-engine.d.ts.map +1 -0
- package/dist/type-inference-engine.js +1151 -0
- package/dist/type-inference-engine.js.map +1 -0
- package/dist/type-rules/type-compatibility-rule.d.ts +85 -0
- package/dist/type-rules/type-compatibility-rule.d.ts.map +1 -0
- package/dist/type-rules/type-compatibility-rule.js +211 -0
- package/dist/type-rules/type-compatibility-rule.js.map +1 -0
- package/package.json +35 -7
- package/README.md +0 -45
|
@@ -0,0 +1,1206 @@
|
|
|
1
|
+
import * as parser from '@babel/parser';
|
|
2
|
+
import _traverse from '@babel/traverse';
|
|
3
|
+
import * as t from '@babel/types';
|
|
4
|
+
const traverse = ((_traverse.default) ?? _traverse);
|
|
5
|
+
import { LibraryLintCache } from './library-lint-cache.js';
|
|
6
|
+
import { TypeInferenceEngine } from './type-inference-engine.js';
|
|
7
|
+
import { ControlFlowAnalyzer } from './control-flow-analyzer.js';
|
|
8
|
+
import { TypeCompatibilityRule } from './type-rules/type-compatibility-rule.js';
|
|
9
|
+
import { ComponentPropRule } from './schema-validation/component-prop-rule.js';
|
|
10
|
+
import { BaseLintRule } from './lint-rule.js';
|
|
11
|
+
import { MJGlobal } from '@memberjunction/global';
|
|
12
|
+
import { GetDialect } from '@memberjunction/sql-dialect';
|
|
13
|
+
// Side-effect import: triggers @RegisterClass decorators on all built-in rules
|
|
14
|
+
import './runtime-rules/index.js';
|
|
15
|
+
export class ComponentLinter {
|
|
16
|
+
static async validateComponentSyntax(code, componentName) {
|
|
17
|
+
try {
|
|
18
|
+
const parseResult = parser.parse(code, {
|
|
19
|
+
sourceType: 'module',
|
|
20
|
+
plugins: ['jsx', 'typescript'],
|
|
21
|
+
errorRecovery: true,
|
|
22
|
+
ranges: true,
|
|
23
|
+
});
|
|
24
|
+
if (parseResult.errors && parseResult.errors.length > 0) {
|
|
25
|
+
const errors = parseResult.errors.map((error) => {
|
|
26
|
+
const location = error.loc ? `Line ${error.loc.line}, Column ${error.loc.column}` : 'Unknown location';
|
|
27
|
+
return `${location}: ${error.message || error.toString()}`;
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
valid: false,
|
|
31
|
+
errors,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
valid: true,
|
|
36
|
+
errors: [],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
// Handle catastrophic parse failures
|
|
41
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error';
|
|
42
|
+
return {
|
|
43
|
+
valid: false,
|
|
44
|
+
errors: [`Failed to parse component: ${errorMessage}`],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The SQL dialect used for WHERE clause validation in semantic validators.
|
|
50
|
+
* Defaults to SQL Server. Set via the `sqlDialect` parameter on `lintComponent()`.
|
|
51
|
+
*/
|
|
52
|
+
static { this._sqlDialect = GetDialect('sqlserver'); }
|
|
53
|
+
/** Current SQL dialect used for WHERE clause parsing */
|
|
54
|
+
static get SqlDialect() {
|
|
55
|
+
return ComponentLinter._sqlDialect;
|
|
56
|
+
}
|
|
57
|
+
static async lintComponent(code, componentName, componentSpec, isRootComponent, contextUser, debugMode, options, sqlDialect) {
|
|
58
|
+
if (sqlDialect) {
|
|
59
|
+
ComponentLinter._sqlDialect = sqlDialect;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
// Require contextUser when libraries need to be checked
|
|
63
|
+
if (componentSpec?.libraries && componentSpec.libraries.length > 0 && !contextUser) {
|
|
64
|
+
throw new Error('contextUser is required when linting components with library dependencies. This is needed to load library-specific lint rules from the database.');
|
|
65
|
+
}
|
|
66
|
+
// Parse with error recovery to get both AST and errors
|
|
67
|
+
const parseResult = parser.parse(code, {
|
|
68
|
+
sourceType: 'module',
|
|
69
|
+
plugins: ['jsx', 'typescript'],
|
|
70
|
+
errorRecovery: true,
|
|
71
|
+
attachComment: false,
|
|
72
|
+
ranges: true,
|
|
73
|
+
tokens: false,
|
|
74
|
+
});
|
|
75
|
+
// Check for syntax errors from parser
|
|
76
|
+
const syntaxViolations = [];
|
|
77
|
+
if (parseResult.errors && parseResult.errors.length > 0) {
|
|
78
|
+
for (const error of parseResult.errors) {
|
|
79
|
+
const err = error; // Babel parser errors don't have proper types
|
|
80
|
+
syntaxViolations.push({
|
|
81
|
+
rule: 'syntax-error',
|
|
82
|
+
severity: 'critical',
|
|
83
|
+
line: err.loc?.line || 0,
|
|
84
|
+
column: err.loc?.column || 0,
|
|
85
|
+
message: `Syntax error in component "${componentName}": ${err.message || err.toString()}`,
|
|
86
|
+
code: err.code || 'BABEL_PARSER_ERROR',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// If we have critical syntax errors, return immediately with those
|
|
91
|
+
if (syntaxViolations.length > 0) {
|
|
92
|
+
// Add suggestions directly to syntax violations
|
|
93
|
+
this.generateSyntaxErrorSuggestions(syntaxViolations);
|
|
94
|
+
return {
|
|
95
|
+
success: false,
|
|
96
|
+
violations: syntaxViolations,
|
|
97
|
+
criticalCount: syntaxViolations.length,
|
|
98
|
+
highCount: 0,
|
|
99
|
+
mediumCount: 0,
|
|
100
|
+
lowCount: 0,
|
|
101
|
+
hasErrors: true,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// Continue with existing linting logic
|
|
105
|
+
const ast = parseResult;
|
|
106
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
107
|
+
// PHASE 1 REFACTOR: Run Type Inference ONCE before all rules
|
|
108
|
+
// This creates a shared TypeContext that all rules can consume
|
|
109
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
110
|
+
const typeEngine = new TypeInferenceEngine(componentSpec, contextUser);
|
|
111
|
+
const controlFlowAnalyzer = new ControlFlowAnalyzer(ast, componentSpec);
|
|
112
|
+
// Run type inference analysis once
|
|
113
|
+
await typeEngine.analyze(ast);
|
|
114
|
+
const typeContext = typeEngine.getTypeContext();
|
|
115
|
+
// Discover all registered lint rules via ClassFactory
|
|
116
|
+
const ruleRegistrations = MJGlobal.Instance.ClassFactory.GetAllRegistrations(BaseLintRule);
|
|
117
|
+
const allRules = [];
|
|
118
|
+
for (const reg of ruleRegistrations) {
|
|
119
|
+
if (!reg.Key)
|
|
120
|
+
continue; // Skip the base class registration (no key)
|
|
121
|
+
const instance = MJGlobal.Instance.ClassFactory.CreateInstance(BaseLintRule, reg.Key);
|
|
122
|
+
if (instance)
|
|
123
|
+
allRules.push(instance);
|
|
124
|
+
}
|
|
125
|
+
// Filter rules based on component type
|
|
126
|
+
const applicableRules = isRootComponent
|
|
127
|
+
? allRules.filter(rule => rule.AppliesTo === 'all' || rule.AppliesTo === 'root')
|
|
128
|
+
: allRules.filter(rule => rule.AppliesTo === 'all' || rule.AppliesTo === 'child');
|
|
129
|
+
const violations = [];
|
|
130
|
+
// Run each rule with error handling to prevent crashes
|
|
131
|
+
for (const rule of applicableRules) {
|
|
132
|
+
try {
|
|
133
|
+
const ruleViolations = rule.Test(ast, componentName, componentSpec, options, typeContext);
|
|
134
|
+
violations.push(...ruleViolations);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
console.warn(`Rule "${rule.Name}" failed during execution:`, error instanceof Error ? error.message : error);
|
|
138
|
+
if (debugMode) {
|
|
139
|
+
console.error('Full error:', error);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
144
|
+
// PHASE 1 REFACTOR: Run new TypeCompatibilityRule with shared context
|
|
145
|
+
// This consolidates all type checking into a single rule
|
|
146
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
147
|
+
try {
|
|
148
|
+
const typeCompatRule = new TypeCompatibilityRule();
|
|
149
|
+
const lintContext = {
|
|
150
|
+
componentName,
|
|
151
|
+
componentSpec,
|
|
152
|
+
typeContext,
|
|
153
|
+
typeEngine,
|
|
154
|
+
controlFlowAnalyzer,
|
|
155
|
+
sqlDialect: ComponentLinter._sqlDialect,
|
|
156
|
+
};
|
|
157
|
+
const typeViolations = typeCompatRule.validate(ast, lintContext);
|
|
158
|
+
violations.push(...typeViolations);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
console.warn('TypeCompatibilityRule failed during execution:', error instanceof Error ? error.message : error);
|
|
162
|
+
if (debugMode) {
|
|
163
|
+
console.error('Full error:', error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
167
|
+
// PHASE 3 REFACTOR: Run new ComponentPropRule with shared context
|
|
168
|
+
// This consolidates all component prop validation into a single rule:
|
|
169
|
+
// - Prop existence (from dependency-prop-validation)
|
|
170
|
+
// - Required props checking
|
|
171
|
+
// - Prop type validation (via TypeContext)
|
|
172
|
+
// - Semantic constraint validation (via SemanticValidators)
|
|
173
|
+
// - Unknown props warning
|
|
174
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
175
|
+
try {
|
|
176
|
+
const componentPropRule = new ComponentPropRule();
|
|
177
|
+
const lintContext = {
|
|
178
|
+
componentName,
|
|
179
|
+
componentSpec,
|
|
180
|
+
typeContext,
|
|
181
|
+
typeEngine,
|
|
182
|
+
controlFlowAnalyzer,
|
|
183
|
+
sqlDialect: ComponentLinter._sqlDialect,
|
|
184
|
+
componentResolver: options?.componentResolver,
|
|
185
|
+
};
|
|
186
|
+
const propViolations = componentPropRule.validate(ast, lintContext);
|
|
187
|
+
violations.push(...propViolations);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
console.warn('ComponentPropRule failed during execution:', error instanceof Error ? error.message : error);
|
|
191
|
+
if (debugMode) {
|
|
192
|
+
console.error('Full error:', error);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Add data requirements validation if componentSpec is provided
|
|
196
|
+
if (componentSpec?.dataRequirements?.entities) {
|
|
197
|
+
try {
|
|
198
|
+
const dataViolations = this.validateDataRequirements(ast, componentSpec, options);
|
|
199
|
+
violations.push(...dataViolations);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
console.warn('Data requirements validation failed:', error instanceof Error ? error.message : error);
|
|
203
|
+
if (debugMode) {
|
|
204
|
+
console.error('Full error:', error);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Apply library-specific lint rules if available
|
|
209
|
+
if (componentSpec?.libraries) {
|
|
210
|
+
const libraryViolations = await this.applyLibraryLintRules(ast, componentSpec, contextUser, debugMode);
|
|
211
|
+
violations.push(...libraryViolations);
|
|
212
|
+
}
|
|
213
|
+
// Deduplicate violations - keep only unique rule+message combinations
|
|
214
|
+
const uniqueViolations = this.deduplicateViolations(violations);
|
|
215
|
+
// Count violations by severity
|
|
216
|
+
const criticalCount = uniqueViolations.filter((v) => v.severity === 'critical').length;
|
|
217
|
+
const highCount = uniqueViolations.filter((v) => v.severity === 'high').length;
|
|
218
|
+
const mediumCount = uniqueViolations.filter((v) => v.severity === 'medium').length;
|
|
219
|
+
const lowCount = uniqueViolations.filter((v) => v.severity === 'low').length;
|
|
220
|
+
// Debug mode summary
|
|
221
|
+
if (debugMode && uniqueViolations.length > 0) {
|
|
222
|
+
console.log('\n' + '='.repeat(60));
|
|
223
|
+
console.log('📊 LINT SUMMARY:');
|
|
224
|
+
console.log('='.repeat(60));
|
|
225
|
+
if (criticalCount > 0)
|
|
226
|
+
console.log(` 🔴 Critical: ${criticalCount}`);
|
|
227
|
+
if (highCount > 0)
|
|
228
|
+
console.log(` 🟠 High: ${highCount}`);
|
|
229
|
+
if (mediumCount > 0)
|
|
230
|
+
console.log(` 🟡 Medium: ${mediumCount}`);
|
|
231
|
+
if (lowCount > 0)
|
|
232
|
+
console.log(` 🟢 Low: ${lowCount}`);
|
|
233
|
+
console.log('='.repeat(60));
|
|
234
|
+
// Group violations by library
|
|
235
|
+
const libraryViolations = uniqueViolations.filter((v) => v.rule.includes('-validator'));
|
|
236
|
+
if (libraryViolations.length > 0) {
|
|
237
|
+
console.log('\n📚 Library-Specific Issues:');
|
|
238
|
+
const byLibrary = new Map();
|
|
239
|
+
libraryViolations.forEach((v) => {
|
|
240
|
+
const lib = v.rule.replace('-validator', '');
|
|
241
|
+
if (!byLibrary.has(lib))
|
|
242
|
+
byLibrary.set(lib, []);
|
|
243
|
+
byLibrary.get(lib).push(v);
|
|
244
|
+
});
|
|
245
|
+
byLibrary.forEach((violations, library) => {
|
|
246
|
+
console.log(` • ${library}: ${violations.length} issue${violations.length > 1 ? 's' : ''}`);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
console.log('');
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
success: criticalCount === 0 && highCount === 0, // Only fail on critical/high
|
|
253
|
+
violations: uniqueViolations,
|
|
254
|
+
criticalCount,
|
|
255
|
+
highCount,
|
|
256
|
+
mediumCount,
|
|
257
|
+
lowCount,
|
|
258
|
+
hasErrors: criticalCount > 0 || highCount > 0,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
// If parsing fails, return a parse error
|
|
263
|
+
// Log stack trace for debugging
|
|
264
|
+
if (error instanceof Error && error.stack) {
|
|
265
|
+
console.error('Parse error stack trace:', error.stack);
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
success: false,
|
|
269
|
+
violations: [
|
|
270
|
+
{
|
|
271
|
+
rule: 'parse-error',
|
|
272
|
+
severity: 'critical',
|
|
273
|
+
line: 0,
|
|
274
|
+
column: 0,
|
|
275
|
+
message: `Failed to parse component: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
hasErrors: true,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
static validateDataRequirements(ast, componentSpec, options) {
|
|
283
|
+
const violations = [];
|
|
284
|
+
// Extract entity names from dataRequirements
|
|
285
|
+
const requiredEntities = new Set();
|
|
286
|
+
const requiredQueries = new Set();
|
|
287
|
+
// Map to store full query definitions for parameter validation
|
|
288
|
+
const queryDefinitionsMap = new Map();
|
|
289
|
+
// Map to track allowed fields per entity (from dataRequirements display/filter/sort arrays)
|
|
290
|
+
const entityFieldsMap = new Map();
|
|
291
|
+
// Map to track ALL fields that exist in the entity
|
|
292
|
+
// Used to distinguish "field not in requirements" (medium) from "field doesn't exist" (critical)
|
|
293
|
+
const entityAllFieldsMap = new Map();
|
|
294
|
+
// FIRST: Populate entityAllFieldsMap from options.entityMetadata if provided
|
|
295
|
+
// This gives us the complete list of fields that actually exist in each entity
|
|
296
|
+
if (options?.entityMetadata && Array.isArray(options.entityMetadata)) {
|
|
297
|
+
for (const entity of options.entityMetadata) {
|
|
298
|
+
if (entity.name && entity.fields) {
|
|
299
|
+
const fieldNames = new Set(entity.fields.map((f) => f.name));
|
|
300
|
+
entityAllFieldsMap.set(entity.name, fieldNames);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (componentSpec.dataRequirements?.entities) {
|
|
305
|
+
for (const entity of componentSpec.dataRequirements.entities) {
|
|
306
|
+
if (entity.name) {
|
|
307
|
+
requiredEntities.add(entity.name);
|
|
308
|
+
entityFieldsMap.set(entity.name, {
|
|
309
|
+
displayFields: new Set(entity.displayFields || []),
|
|
310
|
+
filterFields: new Set(entity.filterFields || []),
|
|
311
|
+
sortFields: new Set(entity.sortFields || []),
|
|
312
|
+
});
|
|
313
|
+
// Build set of ALL fields from fieldMetadata if available
|
|
314
|
+
// Only use fieldMetadata as fallback if entityMetadata wasn't provided for this entity
|
|
315
|
+
if (!entityAllFieldsMap.has(entity.name) && entity.fieldMetadata && Array.isArray(entity.fieldMetadata)) {
|
|
316
|
+
const allFields = new Set();
|
|
317
|
+
for (const field of entity.fieldMetadata) {
|
|
318
|
+
if (field.name) {
|
|
319
|
+
allFields.add(field.name);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
entityAllFieldsMap.set(entity.name, allFields);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (componentSpec.dataRequirements?.queries) {
|
|
328
|
+
for (const query of componentSpec.dataRequirements.queries) {
|
|
329
|
+
if (query.name) {
|
|
330
|
+
requiredQueries.add(query.name);
|
|
331
|
+
queryDefinitionsMap.set(query.name, query);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Also check child components' dataRequirements
|
|
336
|
+
if (componentSpec.dependencies) {
|
|
337
|
+
for (const dep of componentSpec.dependencies) {
|
|
338
|
+
if (dep.dataRequirements?.entities) {
|
|
339
|
+
for (const entity of dep.dataRequirements.entities) {
|
|
340
|
+
if (entity.name) {
|
|
341
|
+
requiredEntities.add(entity.name);
|
|
342
|
+
// Merge fields if entity already exists
|
|
343
|
+
const existing = entityFieldsMap.get(entity.name);
|
|
344
|
+
if (existing) {
|
|
345
|
+
(entity.displayFields || []).forEach((f) => existing.displayFields.add(f));
|
|
346
|
+
(entity.filterFields || []).forEach((f) => existing.filterFields.add(f));
|
|
347
|
+
(entity.sortFields || []).forEach((f) => existing.sortFields.add(f));
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
entityFieldsMap.set(entity.name, {
|
|
351
|
+
displayFields: new Set(entity.displayFields || []),
|
|
352
|
+
filterFields: new Set(entity.filterFields || []),
|
|
353
|
+
sortFields: new Set(entity.sortFields || []),
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
// Merge fieldMetadata into allFields map only if entityMetadata wasn't provided
|
|
357
|
+
// If entityMetadata was provided, it already has the complete field list
|
|
358
|
+
if (!entityAllFieldsMap.has(entity.name) && entity.fieldMetadata && Array.isArray(entity.fieldMetadata)) {
|
|
359
|
+
const existingAll = new Set();
|
|
360
|
+
for (const field of entity.fieldMetadata) {
|
|
361
|
+
if (field.name) {
|
|
362
|
+
existingAll.add(field.name);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
entityAllFieldsMap.set(entity.name, existingAll);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (dep.dataRequirements?.queries) {
|
|
371
|
+
for (const query of dep.dataRequirements.queries) {
|
|
372
|
+
if (query.name) {
|
|
373
|
+
requiredQueries.add(query.name);
|
|
374
|
+
queryDefinitionsMap.set(query.name, query);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Find all RunView, RunViews, and RunQuery calls in the code
|
|
381
|
+
traverse(ast, {
|
|
382
|
+
CallExpression(path) {
|
|
383
|
+
// Check for utilities.rv.RunView or utilities.rv.RunViews pattern
|
|
384
|
+
if (t.isMemberExpression(path.node.callee) &&
|
|
385
|
+
t.isMemberExpression(path.node.callee.object) &&
|
|
386
|
+
t.isIdentifier(path.node.callee.object.object) &&
|
|
387
|
+
path.node.callee.object.object.name === 'utilities' &&
|
|
388
|
+
t.isIdentifier(path.node.callee.object.property) &&
|
|
389
|
+
path.node.callee.object.property.name === 'rv' &&
|
|
390
|
+
t.isIdentifier(path.node.callee.property) &&
|
|
391
|
+
(path.node.callee.property.name === 'RunView' || path.node.callee.property.name === 'RunViews')) {
|
|
392
|
+
// For RunViews, it might be an array of configs
|
|
393
|
+
const configs = path.node.callee.property.name === 'RunViews' && path.node.arguments.length > 0 && t.isArrayExpression(path.node.arguments[0])
|
|
394
|
+
? path.node.arguments[0].elements.filter((e) => t.isObjectExpression(e))
|
|
395
|
+
: path.node.arguments.length > 0 && t.isObjectExpression(path.node.arguments[0])
|
|
396
|
+
? [path.node.arguments[0]]
|
|
397
|
+
: [];
|
|
398
|
+
// Check each config object
|
|
399
|
+
for (const configObj of configs) {
|
|
400
|
+
if (t.isObjectExpression(configObj)) {
|
|
401
|
+
// Find EntityName property
|
|
402
|
+
for (const prop of configObj.properties) {
|
|
403
|
+
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key) && prop.key.name === 'EntityName' && t.isStringLiteral(prop.value)) {
|
|
404
|
+
const usedEntity = prop.value.value;
|
|
405
|
+
// Check if this entity is in the required entities
|
|
406
|
+
if (requiredEntities.size > 0 && !requiredEntities.has(usedEntity)) {
|
|
407
|
+
// Enhanced fuzzy matching for better suggestions
|
|
408
|
+
const possibleMatches = Array.from(requiredEntities).filter((e) => {
|
|
409
|
+
const eLower = e.toLowerCase();
|
|
410
|
+
const usedLower = usedEntity.toLowerCase();
|
|
411
|
+
// Check various matching patterns
|
|
412
|
+
return (
|
|
413
|
+
// Contains match
|
|
414
|
+
eLower.includes(usedLower) ||
|
|
415
|
+
usedLower.includes(eLower) ||
|
|
416
|
+
// Remove spaces and check
|
|
417
|
+
eLower.replace(/\s+/g, '').includes(usedLower.replace(/\s+/g, '')) ||
|
|
418
|
+
usedLower.replace(/\s+/g, '').includes(eLower.replace(/\s+/g, '')) ||
|
|
419
|
+
// Check if the main words match (ignore prefixes like "MJ:")
|
|
420
|
+
eLower.replace(/^mj:\s*/i, '').includes(usedLower) ||
|
|
421
|
+
usedLower.includes(eLower.replace(/^mj:\s*/i, '')));
|
|
422
|
+
});
|
|
423
|
+
// Always show all available entities for clarity
|
|
424
|
+
const allEntities = Array.from(requiredEntities);
|
|
425
|
+
const entityList = allEntities.length <= 5 ? allEntities.join(', ') : allEntities.slice(0, 5).join(', ') + `, ... (${allEntities.length} total)`;
|
|
426
|
+
let message = `Entity "${usedEntity}" not found in dataRequirements.`;
|
|
427
|
+
if (possibleMatches.length > 0) {
|
|
428
|
+
message += ` Did you mean "${possibleMatches[0]}"?`;
|
|
429
|
+
}
|
|
430
|
+
message += ` Available entities: ${entityList}`;
|
|
431
|
+
violations.push({
|
|
432
|
+
rule: 'entity-name-mismatch',
|
|
433
|
+
severity: 'critical',
|
|
434
|
+
line: prop.value.loc?.start.line || 0,
|
|
435
|
+
column: prop.value.loc?.start.column || 0,
|
|
436
|
+
message,
|
|
437
|
+
code: `EntityName: "${usedEntity}"`,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
// Entity is valid, now check fields
|
|
442
|
+
const entityFields = entityFieldsMap.get(usedEntity);
|
|
443
|
+
if (entityFields) {
|
|
444
|
+
// Check Fields array
|
|
445
|
+
const fieldsProperty = configObj.properties.find((p) => t.isObjectProperty(p) && t.isIdentifier(p.key) && p.key.name === 'Fields');
|
|
446
|
+
if (fieldsProperty && t.isObjectProperty(fieldsProperty) && t.isArrayExpression(fieldsProperty.value)) {
|
|
447
|
+
for (const fieldElement of fieldsProperty.value.elements) {
|
|
448
|
+
if (t.isStringLiteral(fieldElement)) {
|
|
449
|
+
const fieldName = fieldElement.value;
|
|
450
|
+
// Check for SQL functions
|
|
451
|
+
if (/COUNT\s*\(|SUM\s*\(|AVG\s*\(|MAX\s*\(|MIN\s*\(/i.test(fieldName)) {
|
|
452
|
+
violations.push({
|
|
453
|
+
rule: 'runview-sql-function',
|
|
454
|
+
severity: 'critical',
|
|
455
|
+
line: fieldElement.loc?.start.line || 0,
|
|
456
|
+
column: fieldElement.loc?.start.column || 0,
|
|
457
|
+
message: `RunView does not support SQL aggregations. Use RunQuery for aggregations or fetch raw data and aggregate in JavaScript.`,
|
|
458
|
+
code: fieldName,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
// Check if field is in allowed fields
|
|
463
|
+
const isAllowed = entityFields.displayFields.has(fieldName) || entityFields.filterFields.has(fieldName) || entityFields.sortFields.has(fieldName);
|
|
464
|
+
if (!isAllowed) {
|
|
465
|
+
// Check if field exists in entity metadata (two-tier severity)
|
|
466
|
+
const allFields = entityAllFieldsMap.get(usedEntity);
|
|
467
|
+
const existsInEntity = allFields ? allFields.has(fieldName) : false;
|
|
468
|
+
if (existsInEntity) {
|
|
469
|
+
// Field exists but not in dataRequirements - medium severity (works but suboptimal)
|
|
470
|
+
violations.push({
|
|
471
|
+
rule: 'field-not-in-requirements',
|
|
472
|
+
severity: 'medium',
|
|
473
|
+
line: fieldElement.loc?.start.line || 0,
|
|
474
|
+
column: fieldElement.loc?.start.column || 0,
|
|
475
|
+
message: `Field "${fieldName}" exists in entity "${usedEntity}" but not declared in dataRequirements. Consider adding to displayFields, filterFields, or sortFields.`,
|
|
476
|
+
code: fieldName,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
// Field doesn't exist in entity - critical severity (will fail at runtime)
|
|
481
|
+
violations.push({
|
|
482
|
+
rule: 'field-not-in-requirements',
|
|
483
|
+
severity: 'critical',
|
|
484
|
+
line: fieldElement.loc?.start.line || 0,
|
|
485
|
+
column: fieldElement.loc?.start.column || 0,
|
|
486
|
+
message: `Field "${fieldName}" does not exist in entity "${usedEntity}". Available fields: ${[
|
|
487
|
+
...entityFields.displayFields,
|
|
488
|
+
...entityFields.filterFields,
|
|
489
|
+
...entityFields.sortFields,
|
|
490
|
+
].join(', ')}`,
|
|
491
|
+
code: fieldName,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
// Check OrderBy field
|
|
500
|
+
const orderByProperty = configObj.properties.find((p) => t.isObjectProperty(p) && t.isIdentifier(p.key) && p.key.name === 'OrderBy');
|
|
501
|
+
if (orderByProperty && t.isObjectProperty(orderByProperty) && t.isStringLiteral(orderByProperty.value)) {
|
|
502
|
+
const orderByValue = orderByProperty.value.value;
|
|
503
|
+
// Extract field name from OrderBy (e.g., "AccountName ASC" -> "AccountName")
|
|
504
|
+
const orderByField = orderByValue.split(/\s+/)[0];
|
|
505
|
+
if (!entityFields.sortFields.has(orderByField)) {
|
|
506
|
+
// Check if field exists in entity metadata (two-tier severity)
|
|
507
|
+
const allFields = entityAllFieldsMap.get(usedEntity);
|
|
508
|
+
const existsInEntity = allFields ? allFields.has(orderByField) : false;
|
|
509
|
+
if (existsInEntity) {
|
|
510
|
+
// Field exists but not in sortFields - medium severity (works but suboptimal)
|
|
511
|
+
violations.push({
|
|
512
|
+
rule: 'orderby-field-not-sortable',
|
|
513
|
+
severity: 'medium',
|
|
514
|
+
line: orderByProperty.value.loc?.start.line || 0,
|
|
515
|
+
column: orderByProperty.value.loc?.start.column || 0,
|
|
516
|
+
message: `OrderBy field "${orderByField}" exists in entity "${usedEntity}" but not declared in sortFields. Consider adding for optimization.`,
|
|
517
|
+
code: orderByValue,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
// Field doesn't exist in entity - critical severity (will fail at runtime)
|
|
522
|
+
violations.push({
|
|
523
|
+
rule: 'orderby-field-not-sortable',
|
|
524
|
+
severity: 'critical',
|
|
525
|
+
line: orderByProperty.value.loc?.start.line || 0,
|
|
526
|
+
column: orderByProperty.value.loc?.start.column || 0,
|
|
527
|
+
message: `OrderBy field "${orderByField}" does not exist in entity "${usedEntity}". Available sort fields: ${[
|
|
528
|
+
...entityFields.sortFields,
|
|
529
|
+
].join(', ')}`,
|
|
530
|
+
code: orderByValue,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
// Check for utilities.rv.RunQuery pattern
|
|
543
|
+
if (t.isMemberExpression(path.node.callee) &&
|
|
544
|
+
t.isMemberExpression(path.node.callee.object) &&
|
|
545
|
+
t.isIdentifier(path.node.callee.object.object) &&
|
|
546
|
+
path.node.callee.object.object.name === 'utilities' &&
|
|
547
|
+
t.isIdentifier(path.node.callee.object.property) &&
|
|
548
|
+
path.node.callee.object.property.name === 'rv' &&
|
|
549
|
+
t.isIdentifier(path.node.callee.property) &&
|
|
550
|
+
path.node.callee.property.name === 'RunQuery') {
|
|
551
|
+
// Check the first argument (should be an object with QueryName)
|
|
552
|
+
if (path.node.arguments.length > 0 && t.isObjectExpression(path.node.arguments[0])) {
|
|
553
|
+
const configObj = path.node.arguments[0];
|
|
554
|
+
// Find QueryName property
|
|
555
|
+
for (const prop of configObj.properties) {
|
|
556
|
+
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key) && prop.key.name === 'QueryName' && t.isStringLiteral(prop.value)) {
|
|
557
|
+
const usedQuery = prop.value.value;
|
|
558
|
+
// Check if this query is in the required queries
|
|
559
|
+
if (requiredQueries.size > 0 && !requiredQueries.has(usedQuery)) {
|
|
560
|
+
// Enhanced fuzzy matching for better suggestions
|
|
561
|
+
const possibleMatches = Array.from(requiredQueries).filter((q) => {
|
|
562
|
+
const qLower = q.toLowerCase();
|
|
563
|
+
const usedLower = usedQuery.toLowerCase();
|
|
564
|
+
return (
|
|
565
|
+
// Contains match
|
|
566
|
+
qLower.includes(usedLower) ||
|
|
567
|
+
usedLower.includes(qLower) ||
|
|
568
|
+
// Remove spaces and check
|
|
569
|
+
qLower.replace(/\s+/g, '').includes(usedLower.replace(/\s+/g, '')) ||
|
|
570
|
+
usedLower.replace(/\s+/g, '').includes(qLower.replace(/\s+/g, '')));
|
|
571
|
+
});
|
|
572
|
+
// Always show all available queries for clarity
|
|
573
|
+
const allQueries = Array.from(requiredQueries);
|
|
574
|
+
const queryList = allQueries.length <= 5 ? allQueries.join(', ') : allQueries.slice(0, 5).join(', ') + `, ... (${allQueries.length} total)`;
|
|
575
|
+
let message = `Query "${usedQuery}" not found in dataRequirements.`;
|
|
576
|
+
if (possibleMatches.length > 0) {
|
|
577
|
+
message += ` Did you mean "${possibleMatches[0]}"?`;
|
|
578
|
+
}
|
|
579
|
+
if (requiredQueries.size > 0) {
|
|
580
|
+
message += ` Available queries: ${queryList}`;
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
message += ` No queries defined in dataRequirements.`;
|
|
584
|
+
}
|
|
585
|
+
violations.push({
|
|
586
|
+
rule: 'query-name-mismatch',
|
|
587
|
+
severity: 'critical',
|
|
588
|
+
line: prop.value.loc?.start.line || 0,
|
|
589
|
+
column: prop.value.loc?.start.column || 0,
|
|
590
|
+
message,
|
|
591
|
+
code: `QueryName: "${usedQuery}"`,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
else if (queryDefinitionsMap.has(usedQuery)) {
|
|
595
|
+
// Query is valid, now check parameters
|
|
596
|
+
const queryDef = queryDefinitionsMap.get(usedQuery);
|
|
597
|
+
if (queryDef?.parameters && queryDef.parameters.length > 0) {
|
|
598
|
+
// Extract parameters from the RunQuery call
|
|
599
|
+
const paramsInCall = new Map();
|
|
600
|
+
// Look for Parameters property in the config object
|
|
601
|
+
for (const prop of configObj.properties) {
|
|
602
|
+
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key) && prop.key.name === 'Parameters' && t.isObjectExpression(prop.value)) {
|
|
603
|
+
// Extract each parameter from the Parameters object
|
|
604
|
+
for (const paramProp of prop.value.properties) {
|
|
605
|
+
if (t.isObjectProperty(paramProp) && t.isIdentifier(paramProp.key)) {
|
|
606
|
+
paramsInCall.set(paramProp.key.name, paramProp);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
// Check for required parameters
|
|
610
|
+
const requiredParams = queryDef.parameters.filter((p) => p.value !== '@runtime' || p.value === '@runtime');
|
|
611
|
+
for (const reqParam of requiredParams) {
|
|
612
|
+
if (!paramsInCall.has(reqParam.name)) {
|
|
613
|
+
violations.push({
|
|
614
|
+
rule: 'missing-query-parameter',
|
|
615
|
+
severity: 'critical',
|
|
616
|
+
line: prop.value.loc?.start.line || 0,
|
|
617
|
+
column: prop.value.loc?.start.column || 0,
|
|
618
|
+
message: `Missing required parameter "${reqParam.name}" for query "${usedQuery}". ${reqParam.description ? `Description: ${reqParam.description}` : ''}`,
|
|
619
|
+
code: `Parameters: { ${reqParam.name}: ... }`,
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
// Check for unknown parameters
|
|
624
|
+
const validParamNames = new Set(queryDef.parameters.map((p) => p.name));
|
|
625
|
+
for (const [paramName, paramNode] of paramsInCall) {
|
|
626
|
+
if (!validParamNames.has(paramName)) {
|
|
627
|
+
violations.push({
|
|
628
|
+
rule: 'unknown-query-parameter',
|
|
629
|
+
severity: 'high',
|
|
630
|
+
line: paramNode.loc?.start.line || 0,
|
|
631
|
+
column: paramNode.loc?.start.column || 0,
|
|
632
|
+
message: `Unknown parameter "${paramName}" for query "${usedQuery}". Valid parameters: ${Array.from(validParamNames).join(', ')}`,
|
|
633
|
+
code: `${paramName}: ...`,
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
break; // Found Parameters property, no need to continue
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
// If query has parameters but no Parameters property was found in the call
|
|
641
|
+
if (paramsInCall.size === 0 && queryDef?.parameters && queryDef.parameters.length > 0) {
|
|
642
|
+
violations.push({
|
|
643
|
+
rule: 'missing-parameters-object',
|
|
644
|
+
severity: 'critical',
|
|
645
|
+
line: configObj.loc?.start.line || 0,
|
|
646
|
+
column: configObj.loc?.start.column || 0,
|
|
647
|
+
message: `Query "${usedQuery}" requires parameters but none were provided. Required parameters: ${queryDef.parameters.map((p) => p.name).join(', ')}`,
|
|
648
|
+
code: `RunQuery({ QueryName: "${usedQuery}", Parameters: { ... } })`,
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
});
|
|
659
|
+
return violations;
|
|
660
|
+
}
|
|
661
|
+
static deduplicateViolations(violations) {
|
|
662
|
+
const seen = new Set();
|
|
663
|
+
const unique = [];
|
|
664
|
+
for (const violation of violations) {
|
|
665
|
+
// Create a key from the complete violation details (case-insensitive for message)
|
|
666
|
+
const key = `${violation.rule}:${violation.severity}:${violation.line}:${violation.column}:${violation.message.toLowerCase()}`;
|
|
667
|
+
if (!seen.has(key)) {
|
|
668
|
+
seen.add(key);
|
|
669
|
+
unique.push(violation);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
// Sort by severity (critical > high > medium > low) and then by line number
|
|
673
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
674
|
+
unique.sort((a, b) => {
|
|
675
|
+
const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
|
|
676
|
+
if (severityDiff !== 0)
|
|
677
|
+
return severityDiff;
|
|
678
|
+
return a.line - b.line;
|
|
679
|
+
});
|
|
680
|
+
return unique;
|
|
681
|
+
}
|
|
682
|
+
static generateSyntaxErrorSuggestions(violations) {
|
|
683
|
+
for (const violation of violations) {
|
|
684
|
+
if (violation.message.includes('Unterminated string')) {
|
|
685
|
+
violation.suggestion = {
|
|
686
|
+
text: 'Check that all string literals are properly closed with matching quotes',
|
|
687
|
+
example: 'Template literals with interpolation must use backticks: `text ${variable} text`',
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
else if (violation.message.includes('Unexpected token') || violation.message.includes('export')) {
|
|
691
|
+
violation.suggestion = {
|
|
692
|
+
text: 'Ensure all code is within the component function body',
|
|
693
|
+
example: 'Remove any export statements or code outside the function definition',
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
else if (violation.message.includes('import') && violation.message.includes('top level')) {
|
|
697
|
+
violation.suggestion = {
|
|
698
|
+
text: 'Import statements are not allowed in components - use props instead',
|
|
699
|
+
example: 'Access libraries through props: const { React, MaterialUI } = props.components',
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
violation.suggestion = {
|
|
704
|
+
text: 'Fix the syntax error before the component can be compiled',
|
|
705
|
+
example: 'Review the code at the specified line and column for syntax issues',
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Apply library-specific lint rules based on ComponentLibrary LintRules field
|
|
712
|
+
*/
|
|
713
|
+
static async applyLibraryLintRules(ast, componentSpec, contextUser, debugMode) {
|
|
714
|
+
const violations = [];
|
|
715
|
+
try {
|
|
716
|
+
// Use the cached and compiled library rules
|
|
717
|
+
const cache = LibraryLintCache.getInstance();
|
|
718
|
+
await cache.loadLibraryRules(contextUser);
|
|
719
|
+
// Check each library that this component uses
|
|
720
|
+
if (componentSpec.libraries) {
|
|
721
|
+
// Run library checks in parallel for performance
|
|
722
|
+
const libraryPromises = componentSpec.libraries.map(async (lib) => {
|
|
723
|
+
const libraryViolations = [];
|
|
724
|
+
// Get the cached and compiled rules for this library
|
|
725
|
+
const compiledRules = cache.getLibraryRules(lib.name);
|
|
726
|
+
if (debugMode) {
|
|
727
|
+
console.log(`\n 📚 Library: ${lib.name}`);
|
|
728
|
+
if (compiledRules) {
|
|
729
|
+
console.log(` ┌─ Has lint rules: ✅`);
|
|
730
|
+
if (compiledRules.validators) {
|
|
731
|
+
console.log(` ├─ Validators: ${Object.keys(compiledRules.validators).length}`);
|
|
732
|
+
}
|
|
733
|
+
if (compiledRules.initialization) {
|
|
734
|
+
console.log(` ├─ Initialization rules: ✅`);
|
|
735
|
+
}
|
|
736
|
+
if (compiledRules.lifecycle) {
|
|
737
|
+
console.log(` ├─ Lifecycle rules: ✅`);
|
|
738
|
+
}
|
|
739
|
+
console.log(` └─ Starting checks...`);
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
console.log(` └─ No lint rules defined`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
if (compiledRules) {
|
|
746
|
+
const library = compiledRules.library;
|
|
747
|
+
const libraryName = library.Name || lib.name;
|
|
748
|
+
// Apply initialization rules
|
|
749
|
+
if (compiledRules.initialization) {
|
|
750
|
+
if (debugMode) {
|
|
751
|
+
console.log(` ├─ 🔍 Checking ${libraryName} initialization patterns...`);
|
|
752
|
+
}
|
|
753
|
+
const initViolations = this.checkLibraryInitialization(ast, libraryName, compiledRules.initialization);
|
|
754
|
+
// Debug logging for library violations
|
|
755
|
+
if (debugMode && initViolations.length > 0) {
|
|
756
|
+
console.log(` │ ⚠️ Found ${initViolations.length} initialization issue${initViolations.length > 1 ? 's' : ''}`);
|
|
757
|
+
initViolations.forEach((v) => {
|
|
758
|
+
const icon = v.severity === 'critical' ? '🔴' : v.severity === 'high' ? '🟠' : v.severity === 'medium' ? '🟡' : '🟢';
|
|
759
|
+
console.log(` │ ${icon} Line ${v.line}: ${v.message}`);
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
libraryViolations.push(...initViolations);
|
|
763
|
+
}
|
|
764
|
+
// Apply lifecycle rules
|
|
765
|
+
if (compiledRules.lifecycle) {
|
|
766
|
+
if (debugMode) {
|
|
767
|
+
console.log(` ├─ 🔄 Checking ${libraryName} lifecycle management...`);
|
|
768
|
+
}
|
|
769
|
+
const lifecycleViolations = this.checkLibraryLifecycle(ast, libraryName, compiledRules.lifecycle);
|
|
770
|
+
// Debug logging for library violations
|
|
771
|
+
if (debugMode && lifecycleViolations.length > 0) {
|
|
772
|
+
console.log(` │ ⚠️ Found ${lifecycleViolations.length} lifecycle issue${lifecycleViolations.length > 1 ? 's' : ''}`);
|
|
773
|
+
lifecycleViolations.forEach((v) => {
|
|
774
|
+
const icon = v.severity === 'critical' ? '🔴' : v.severity === 'high' ? '🟠' : v.severity === 'medium' ? '🟡' : '🟢';
|
|
775
|
+
console.log(` │ ${icon} Line ${v.line}: ${v.message}`);
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
libraryViolations.push(...lifecycleViolations);
|
|
779
|
+
}
|
|
780
|
+
// Apply options validation
|
|
781
|
+
if (compiledRules.options) {
|
|
782
|
+
if (debugMode) {
|
|
783
|
+
console.log(` ├─ ⚙️ Checking ${libraryName} configuration options...`);
|
|
784
|
+
}
|
|
785
|
+
const optionsViolations = this.checkLibraryOptions(ast, libraryName, compiledRules.options);
|
|
786
|
+
// Debug logging for library violations
|
|
787
|
+
if (debugMode && optionsViolations.length > 0) {
|
|
788
|
+
console.log(` │ ⚠️ Found ${optionsViolations.length} configuration issue${optionsViolations.length > 1 ? 's' : ''}`);
|
|
789
|
+
optionsViolations.forEach((v) => {
|
|
790
|
+
const icon = v.severity === 'critical' ? '🔴' : v.severity === 'high' ? '🟠' : v.severity === 'medium' ? '🟡' : '🟢';
|
|
791
|
+
console.log(` │ ${icon} Line ${v.line}: ${v.message}`);
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
libraryViolations.push(...optionsViolations);
|
|
795
|
+
}
|
|
796
|
+
// Apply compiled validators (already compiled in cache)
|
|
797
|
+
if (compiledRules.validators) {
|
|
798
|
+
const validatorViolations = this.executeCompiledValidators(ast, libraryName, library.GlobalVariable || '', compiledRules.validators, debugMode);
|
|
799
|
+
libraryViolations.push(...validatorViolations);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return libraryViolations;
|
|
803
|
+
});
|
|
804
|
+
// Wait for all library checks to complete
|
|
805
|
+
const allLibraryViolations = await Promise.all(libraryPromises);
|
|
806
|
+
// Flatten the results
|
|
807
|
+
allLibraryViolations.forEach((libViolations) => {
|
|
808
|
+
violations.push(...libViolations);
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
catch (error) {
|
|
813
|
+
console.warn('Failed to apply library lint rules:', error);
|
|
814
|
+
}
|
|
815
|
+
return violations;
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Check library initialization patterns (constructor, element type, etc.)
|
|
819
|
+
*/
|
|
820
|
+
static checkLibraryInitialization(ast, libraryName, rules) {
|
|
821
|
+
const violations = [];
|
|
822
|
+
traverse(ast, {
|
|
823
|
+
// Check for new ConstructorName() patterns
|
|
824
|
+
NewExpression(path) {
|
|
825
|
+
if (t.isIdentifier(path.node.callee) && path.node.callee.name === rules.constructorName) {
|
|
826
|
+
// Check if it requires 'new' keyword
|
|
827
|
+
if (rules.requiresNew === false) {
|
|
828
|
+
violations.push({
|
|
829
|
+
rule: 'library-initialization',
|
|
830
|
+
severity: 'critical',
|
|
831
|
+
line: path.node.loc?.start.line || 0,
|
|
832
|
+
column: path.node.loc?.start.column || 0,
|
|
833
|
+
message: `${libraryName}: ${rules.constructorName} should not use 'new' keyword`,
|
|
834
|
+
code: `${rules.constructorName}(...) // without new`,
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
// Check element type if first argument is a ref
|
|
838
|
+
if (rules.elementType && path.node.arguments[0]) {
|
|
839
|
+
const firstArg = path.node.arguments[0];
|
|
840
|
+
// Check if it's chartRef.current or similar
|
|
841
|
+
if (t.isMemberExpression(firstArg) && t.isIdentifier(firstArg.property) && firstArg.property.name === 'current') {
|
|
842
|
+
// Try to find what element the ref is attached to
|
|
843
|
+
const refName = t.isIdentifier(firstArg.object) ? firstArg.object.name : null;
|
|
844
|
+
if (refName) {
|
|
845
|
+
ComponentLinter.checkRefElementType(ast, refName, rules.elementType, libraryName, violations);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
},
|
|
851
|
+
// Check for function calls without new (if requiresNew is true)
|
|
852
|
+
CallExpression(path) {
|
|
853
|
+
if (t.isIdentifier(path.node.callee) && path.node.callee.name === rules.constructorName && rules.requiresNew === true) {
|
|
854
|
+
violations.push({
|
|
855
|
+
rule: 'library-initialization',
|
|
856
|
+
severity: 'critical',
|
|
857
|
+
line: path.node.loc?.start.line || 0,
|
|
858
|
+
column: path.node.loc?.start.column || 0,
|
|
859
|
+
message: `${libraryName}: ${rules.constructorName} requires 'new' keyword`,
|
|
860
|
+
code: `new ${rules.constructorName}(...)`,
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
},
|
|
864
|
+
});
|
|
865
|
+
return violations;
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Check if a library is directly instantiated in the component code
|
|
869
|
+
* Returns false if the library is only used indirectly (e.g., by dependency components)
|
|
870
|
+
*/
|
|
871
|
+
static isLibraryDirectlyInstantiated(ast, constructorName) {
|
|
872
|
+
let isDirectlyUsed = false;
|
|
873
|
+
traverse(ast, {
|
|
874
|
+
// Check for: new Chart(...), new ApexCharts(...), etc.
|
|
875
|
+
NewExpression(path) {
|
|
876
|
+
if (t.isIdentifier(path.node.callee) && path.node.callee.name === constructorName) {
|
|
877
|
+
isDirectlyUsed = true;
|
|
878
|
+
}
|
|
879
|
+
},
|
|
880
|
+
// Check for: Chart.register(...), Chart.defaults.set(...), etc.
|
|
881
|
+
CallExpression(path) {
|
|
882
|
+
if (t.isMemberExpression(path.node.callee) &&
|
|
883
|
+
t.isIdentifier(path.node.callee.object) &&
|
|
884
|
+
path.node.callee.object.name === constructorName) {
|
|
885
|
+
isDirectlyUsed = true;
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
});
|
|
889
|
+
return isDirectlyUsed;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Check if a ref is attached to the correct element type
|
|
893
|
+
*/
|
|
894
|
+
static checkRefElementType(ast, refName, expectedType, libraryName, violations) {
|
|
895
|
+
traverse(ast, {
|
|
896
|
+
JSXElement(path) {
|
|
897
|
+
const openingElement = path.node.openingElement;
|
|
898
|
+
// Check if this element has a ref attribute
|
|
899
|
+
const refAttr = openingElement.attributes.find((attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === 'ref');
|
|
900
|
+
if (refAttr && t.isJSXAttribute(refAttr)) {
|
|
901
|
+
// Check if the ref value matches our refName
|
|
902
|
+
const refValue = refAttr.value;
|
|
903
|
+
if (t.isJSXExpressionContainer(refValue) && t.isIdentifier(refValue.expression) && refValue.expression.name === refName) {
|
|
904
|
+
// Check element type
|
|
905
|
+
const elementName = t.isJSXIdentifier(openingElement.name) ? openingElement.name.name : '';
|
|
906
|
+
if (elementName.toLowerCase() !== expectedType.toLowerCase()) {
|
|
907
|
+
violations.push({
|
|
908
|
+
rule: 'library-element-type',
|
|
909
|
+
severity: 'critical',
|
|
910
|
+
line: openingElement.loc?.start.line || 0,
|
|
911
|
+
column: openingElement.loc?.start.column || 0,
|
|
912
|
+
message: `${libraryName} requires a <${expectedType}> element, not <${elementName}>`,
|
|
913
|
+
code: `<${expectedType} ref={${refName}}>`,
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Check library lifecycle methods (render, destroy, etc.)
|
|
923
|
+
*/
|
|
924
|
+
static checkLibraryLifecycle(ast, libraryName, rules) {
|
|
925
|
+
const violations = [];
|
|
926
|
+
// Track which methods are called
|
|
927
|
+
const calledMethods = new Set();
|
|
928
|
+
const instanceVariables = new Set();
|
|
929
|
+
traverse(ast, {
|
|
930
|
+
// Track instance variables
|
|
931
|
+
VariableDeclarator(path) {
|
|
932
|
+
if (t.isNewExpression(path.node.init) && t.isIdentifier(path.node.init.callee)) {
|
|
933
|
+
if (t.isIdentifier(path.node.id)) {
|
|
934
|
+
instanceVariables.add(path.node.id.name);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
},
|
|
938
|
+
// Track method calls
|
|
939
|
+
CallExpression(path) {
|
|
940
|
+
if (t.isMemberExpression(path.node.callee)) {
|
|
941
|
+
const callee = path.node.callee;
|
|
942
|
+
if (t.isIdentifier(callee.property)) {
|
|
943
|
+
const methodName = callee.property.name;
|
|
944
|
+
const objectName = t.isIdentifier(callee.object) ? callee.object.name : null;
|
|
945
|
+
if (objectName && instanceVariables.has(objectName)) {
|
|
946
|
+
calledMethods.add(methodName);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
},
|
|
951
|
+
});
|
|
952
|
+
// Check required methods
|
|
953
|
+
if (rules.requiredMethods) {
|
|
954
|
+
for (const method of rules.requiredMethods) {
|
|
955
|
+
if (!calledMethods.has(method)) {
|
|
956
|
+
violations.push({
|
|
957
|
+
rule: 'library-lifecycle',
|
|
958
|
+
severity: 'high',
|
|
959
|
+
line: 0,
|
|
960
|
+
column: 0,
|
|
961
|
+
message: `${libraryName}: Missing required method call '${method}()' after initialization`,
|
|
962
|
+
code: `instance.${method}()`,
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
// Check cleanup in useEffect
|
|
968
|
+
if (rules.cleanupMethods && rules.cleanupMethods.length > 0) {
|
|
969
|
+
// First, check if the library is directly instantiated in the component
|
|
970
|
+
// If it's only used by dependency components, skip cleanup check
|
|
971
|
+
const isLibraryDirectlyUsed = ComponentLinter.isLibraryDirectlyInstantiated(ast, rules.constructorName);
|
|
972
|
+
if (!isLibraryDirectlyUsed) {
|
|
973
|
+
// Library is only used indirectly (e.g., by dependency components)
|
|
974
|
+
// No cleanup check needed
|
|
975
|
+
return violations;
|
|
976
|
+
}
|
|
977
|
+
let hasCleanup = false;
|
|
978
|
+
// Track local variables that are instances of the library
|
|
979
|
+
const libraryInstances = new Set();
|
|
980
|
+
traverse(ast, {
|
|
981
|
+
CallExpression(path) {
|
|
982
|
+
// Check for both useEffect and React.useEffect
|
|
983
|
+
const isUseEffect = (t.isIdentifier(path.node.callee) && path.node.callee.name === 'useEffect') ||
|
|
984
|
+
(t.isMemberExpression(path.node.callee) &&
|
|
985
|
+
t.isIdentifier(path.node.callee.object) &&
|
|
986
|
+
path.node.callee.object.name === 'React' &&
|
|
987
|
+
t.isIdentifier(path.node.callee.property) &&
|
|
988
|
+
path.node.callee.property.name === 'useEffect');
|
|
989
|
+
if (isUseEffect) {
|
|
990
|
+
const firstArg = path.node.arguments[0];
|
|
991
|
+
if (t.isArrowFunctionExpression(firstArg) || t.isFunctionExpression(firstArg)) {
|
|
992
|
+
// First, identify local variables that are library instances
|
|
993
|
+
// e.g., const chart = new ApexCharts(...)
|
|
994
|
+
libraryInstances.clear();
|
|
995
|
+
traverse(firstArg, {
|
|
996
|
+
VariableDeclarator(varPath) {
|
|
997
|
+
if (t.isIdentifier(varPath.node.id) &&
|
|
998
|
+
t.isNewExpression(varPath.node.init) &&
|
|
999
|
+
t.isIdentifier(varPath.node.init.callee) &&
|
|
1000
|
+
varPath.node.init.callee.name === rules.constructorName) {
|
|
1001
|
+
libraryInstances.add(varPath.node.id.name);
|
|
1002
|
+
}
|
|
1003
|
+
},
|
|
1004
|
+
}, path.scope, path.state, path);
|
|
1005
|
+
// Check if it returns a cleanup function
|
|
1006
|
+
traverse(firstArg, {
|
|
1007
|
+
ReturnStatement(returnPath) {
|
|
1008
|
+
if (t.isArrowFunctionExpression(returnPath.node.argument) || t.isFunctionExpression(returnPath.node.argument)) {
|
|
1009
|
+
// Check if cleanup function calls destroy
|
|
1010
|
+
traverse(returnPath.node.argument, {
|
|
1011
|
+
CallExpression(cleanupPath) {
|
|
1012
|
+
if (t.isMemberExpression(cleanupPath.node.callee)) {
|
|
1013
|
+
const callee = cleanupPath.node.callee;
|
|
1014
|
+
// Check if the method name is a cleanup method
|
|
1015
|
+
if (t.isIdentifier(callee.property) && rules.cleanupMethods.includes(callee.property.name)) {
|
|
1016
|
+
// Pattern 1: instance.destroy() where instance is a tracked library instance
|
|
1017
|
+
// e.g., chart.destroy() where chart is from const chart = new ApexCharts(...)
|
|
1018
|
+
if (t.isIdentifier(callee.object) && libraryInstances.has(callee.object.name)) {
|
|
1019
|
+
hasCleanup = true;
|
|
1020
|
+
}
|
|
1021
|
+
// Pattern 2: ref.current.destroy() or ref.current._chart.destroy()
|
|
1022
|
+
// Any member expression chain ending in cleanup method
|
|
1023
|
+
// e.g., chartRef.current._chart.destroy()
|
|
1024
|
+
else if (t.isMemberExpression(callee.object)) {
|
|
1025
|
+
hasCleanup = true;
|
|
1026
|
+
}
|
|
1027
|
+
// Pattern 3: d3.select(...).selectAll('*').remove()
|
|
1028
|
+
// Chained method calls ending in cleanup
|
|
1029
|
+
else if (t.isCallExpression(callee.object)) {
|
|
1030
|
+
hasCleanup = true;
|
|
1031
|
+
}
|
|
1032
|
+
// Pattern 4: Any identifier calling cleanup method
|
|
1033
|
+
// e.g., selection.remove() where selection = d3.select(...)
|
|
1034
|
+
else if (t.isIdentifier(callee.object)) {
|
|
1035
|
+
hasCleanup = true;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
},
|
|
1040
|
+
}, returnPath.scope, returnPath.state, returnPath);
|
|
1041
|
+
}
|
|
1042
|
+
},
|
|
1043
|
+
}, path.scope, path.state, path);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
},
|
|
1047
|
+
});
|
|
1048
|
+
if (!hasCleanup) {
|
|
1049
|
+
violations.push({
|
|
1050
|
+
rule: 'library-cleanup',
|
|
1051
|
+
severity: 'medium',
|
|
1052
|
+
line: 0,
|
|
1053
|
+
column: 0,
|
|
1054
|
+
message: `${libraryName}: Missing cleanup in useEffect. Call ${rules.cleanupMethods.join(' or ')} in cleanup function`,
|
|
1055
|
+
code: `useEffect(() => {\n // ... initialization\n return () => {\n instance.${rules.cleanupMethods[0]}();\n };\n}, []);`,
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return violations;
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Check library options and configuration
|
|
1063
|
+
*/
|
|
1064
|
+
static checkLibraryOptions(ast, libraryName, rules) {
|
|
1065
|
+
const violations = [];
|
|
1066
|
+
traverse(ast, {
|
|
1067
|
+
ObjectExpression(path) {
|
|
1068
|
+
// Check if this might be a config object for the library
|
|
1069
|
+
const properties = path.node.properties.filter((p) => t.isObjectProperty(p));
|
|
1070
|
+
const propNames = properties.filter((p) => t.isIdentifier(p.key)).map((p) => p.key.name);
|
|
1071
|
+
// Check for required properties
|
|
1072
|
+
if (rules.requiredProperties) {
|
|
1073
|
+
const hasChartType = propNames.some((name) => rules.requiredProperties.includes(name));
|
|
1074
|
+
if (hasChartType) {
|
|
1075
|
+
// This looks like a config object, check all required props
|
|
1076
|
+
for (const required of rules.requiredProperties) {
|
|
1077
|
+
if (!propNames.includes(required)) {
|
|
1078
|
+
violations.push({
|
|
1079
|
+
rule: 'library-options',
|
|
1080
|
+
severity: 'high',
|
|
1081
|
+
line: path.node.loc?.start.line || 0,
|
|
1082
|
+
column: path.node.loc?.start.column || 0,
|
|
1083
|
+
message: `${libraryName}: Missing required option '${required}'`,
|
|
1084
|
+
code: `${required}: /* value */`,
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
// Check property types
|
|
1091
|
+
if (rules.propertyTypes) {
|
|
1092
|
+
for (const prop of properties) {
|
|
1093
|
+
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
|
|
1094
|
+
const propName = prop.key.name;
|
|
1095
|
+
const expectedType = rules.propertyTypes[propName];
|
|
1096
|
+
if (expectedType) {
|
|
1097
|
+
// Check if the value matches expected type
|
|
1098
|
+
if (expectedType.includes('array') && !t.isArrayExpression(prop.value)) {
|
|
1099
|
+
violations.push({
|
|
1100
|
+
rule: 'library-options',
|
|
1101
|
+
severity: 'medium',
|
|
1102
|
+
line: prop.loc?.start.line || 0,
|
|
1103
|
+
column: prop.loc?.start.column || 0,
|
|
1104
|
+
message: `${libraryName}: Option '${propName}' should be an array`,
|
|
1105
|
+
code: `${propName}: []`,
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
},
|
|
1113
|
+
});
|
|
1114
|
+
return violations;
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Execute pre-compiled validators from cache
|
|
1118
|
+
*/
|
|
1119
|
+
static executeCompiledValidators(ast, libraryName, globalVariable, validators, debugMode) {
|
|
1120
|
+
const violations = [];
|
|
1121
|
+
// Create context object for validators
|
|
1122
|
+
const context = {
|
|
1123
|
+
libraryName,
|
|
1124
|
+
globalVariable,
|
|
1125
|
+
instanceVariables: new Set(),
|
|
1126
|
+
violations: [], // Validators push violations here
|
|
1127
|
+
};
|
|
1128
|
+
// First pass: identify library instance variables
|
|
1129
|
+
traverse(ast, {
|
|
1130
|
+
VariableDeclarator(path) {
|
|
1131
|
+
if (t.isNewExpression(path.node.init) && t.isIdentifier(path.node.init.callee)) {
|
|
1132
|
+
// Check if it's a library constructor
|
|
1133
|
+
if (path.node.init.callee.name === globalVariable) {
|
|
1134
|
+
if (t.isIdentifier(path.node.id)) {
|
|
1135
|
+
context.instanceVariables.add(path.node.id.name);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
},
|
|
1140
|
+
});
|
|
1141
|
+
// Execute each compiled validator
|
|
1142
|
+
for (const [validatorName, validator] of Object.entries(validators)) {
|
|
1143
|
+
if (validator && validator.validateFn) {
|
|
1144
|
+
const beforeCount = context.violations.length;
|
|
1145
|
+
// Log that we're running this specific validator
|
|
1146
|
+
if (debugMode) {
|
|
1147
|
+
console.log(` ├─ 🔬 Running ${libraryName} validator: ${validatorName}`);
|
|
1148
|
+
if (validator.description) {
|
|
1149
|
+
console.log(` │ ℹ️ ${validator.description}`);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
// Traverse AST and apply validator
|
|
1153
|
+
traverse(ast, {
|
|
1154
|
+
enter(path) {
|
|
1155
|
+
try {
|
|
1156
|
+
// Validators don't return violations, they push to context.violations
|
|
1157
|
+
validator.validateFn(ast, path, t, context);
|
|
1158
|
+
}
|
|
1159
|
+
catch (error) {
|
|
1160
|
+
// Validator execution error - log but don't crash
|
|
1161
|
+
console.warn(`Validator ${validatorName} failed:`, error);
|
|
1162
|
+
if (debugMode) {
|
|
1163
|
+
console.error('Full error:', error);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
},
|
|
1167
|
+
});
|
|
1168
|
+
// Debug logging for this specific validator
|
|
1169
|
+
const newViolations = context.violations.length - beforeCount;
|
|
1170
|
+
if (debugMode && newViolations > 0) {
|
|
1171
|
+
console.log(` │ ✓ Found ${newViolations} violation${newViolations > 1 ? 's' : ''}`);
|
|
1172
|
+
// Show the violations from this validator
|
|
1173
|
+
const validatorViolations = context.violations.slice(beforeCount);
|
|
1174
|
+
validatorViolations.forEach((v) => {
|
|
1175
|
+
const icon = v.type === 'error' || v.severity === 'critical'
|
|
1176
|
+
? '🔴'
|
|
1177
|
+
: v.type === 'warning' || v.severity === 'high'
|
|
1178
|
+
? '🟠'
|
|
1179
|
+
: v.severity === 'medium'
|
|
1180
|
+
? '🟡'
|
|
1181
|
+
: '🟢';
|
|
1182
|
+
console.log(` │ ${icon} Line ${v.line || 'unknown'}: ${v.message}`);
|
|
1183
|
+
if (v.suggestion) {
|
|
1184
|
+
console.log(` │ 💡 ${v.suggestion}`);
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
else if (debugMode) {
|
|
1189
|
+
console.log(` │ ✓ No violations found`);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
// Convert context violations to standard format
|
|
1194
|
+
const standardViolations = context.violations.map((v) => ({
|
|
1195
|
+
rule: `${libraryName.toLowerCase()}-validator`,
|
|
1196
|
+
severity: v.severity || (v.type === 'error' ? 'critical' : v.type === 'warning' ? 'high' : 'medium'),
|
|
1197
|
+
line: v.line || 0,
|
|
1198
|
+
column: v.column || 0,
|
|
1199
|
+
message: v.message,
|
|
1200
|
+
code: v.code,
|
|
1201
|
+
}));
|
|
1202
|
+
violations.push(...standardViolations);
|
|
1203
|
+
return violations;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
//# sourceMappingURL=component-linter.js.map
|