@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,886 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { traverse, isStringLike, isNumberLike, isObjectLike } from '../lint-utils.js';
|
|
8
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
9
|
+
import * as t from '@babel/types';
|
|
10
|
+
import { BaseLintRule } from '../lint-rule.js';
|
|
11
|
+
import { mapSQLTypeToJSType } from '../type-context.js';
|
|
12
|
+
/**
|
|
13
|
+
* Rule: runquery-call-validation
|
|
14
|
+
*
|
|
15
|
+
* Consolidates all RunQuery CALL SITE validation into a single traversal.
|
|
16
|
+
* Absorbs checks from:
|
|
17
|
+
* - runview-runquery-valid-properties (RunQuery portion)
|
|
18
|
+
* - runquery-parameters-validation (all)
|
|
19
|
+
* - query-parameter-type-validation (all)
|
|
20
|
+
* - query-param-null-check (all)
|
|
21
|
+
* - runquery-missing-categorypath (all)
|
|
22
|
+
* - runquery-runview-validation (all)
|
|
23
|
+
*
|
|
24
|
+
* Severity: critical/high/medium
|
|
25
|
+
* Applies to: all components
|
|
26
|
+
*/
|
|
27
|
+
const RULE_NAME = 'runquery-call-validation';
|
|
28
|
+
/** Valid properties for RunQuery */
|
|
29
|
+
const VALID_RUNQUERY_PROPS = new Set([
|
|
30
|
+
'QueryID', 'QueryName', 'CategoryID', 'CategoryPath',
|
|
31
|
+
'Parameters', 'MaxRows', 'StartRow', 'ForceAuditLog', 'AuditLogDescription',
|
|
32
|
+
]);
|
|
33
|
+
/** SQL keywords for injection detection */
|
|
34
|
+
/**
|
|
35
|
+
* SQL structural patterns that indicate actual SQL statements, not query names.
|
|
36
|
+
* Requires multiple keywords in combination to avoid false positives on
|
|
37
|
+
* domain terms like "Join Year", "Update Status", "Revenue From Events".
|
|
38
|
+
*/
|
|
39
|
+
const SQL_PATTERNS = [
|
|
40
|
+
/\bSELECT\b.*\bFROM\b/i, // SELECT ... FROM
|
|
41
|
+
/\bINSERT\b.*\bINTO\b/i, // INSERT INTO
|
|
42
|
+
/\bUPDATE\b.*\bSET\b/i, // UPDATE ... SET
|
|
43
|
+
/\bDELETE\b.*\bFROM\b/i, // DELETE FROM
|
|
44
|
+
/\bDROP\b\s+\bTABLE\b/i, // DROP TABLE
|
|
45
|
+
/\bALTER\b\s+\bTABLE\b/i, // ALTER TABLE
|
|
46
|
+
/\bCREATE\b\s+\bTABLE\b/i, // CREATE TABLE
|
|
47
|
+
/\bEXEC\b\s+/i, // EXEC sp_...
|
|
48
|
+
/[=;*]\s*$/, // SQL operators at end (WHERE x = 1; or SELECT *)
|
|
49
|
+
];
|
|
50
|
+
/** Scalar SQL types that do not accept array values */
|
|
51
|
+
const SCALAR_SQL_TYPES = new Set([
|
|
52
|
+
'nvarchar', 'varchar', 'char', 'nchar', 'text', 'ntext',
|
|
53
|
+
'int', 'bigint', 'smallint', 'tinyint',
|
|
54
|
+
'decimal', 'numeric', 'float', 'real', 'money', 'smallmoney',
|
|
55
|
+
'bit',
|
|
56
|
+
'date', 'datetime', 'datetime2', 'smalldatetime', 'datetimeoffset', 'time',
|
|
57
|
+
'uniqueidentifier',
|
|
58
|
+
'string', 'number', 'boolean',
|
|
59
|
+
]);
|
|
60
|
+
const DEFAULT_SUGGESTION = {
|
|
61
|
+
text: 'Use only valid properties for RunView/RunViews and RunQuery',
|
|
62
|
+
example: `// ❌ WRONG - Invalid properties on RunView:
|
|
63
|
+
await utilities.rv.RunView({
|
|
64
|
+
EntityName: 'MJ: AI Prompt Runs',
|
|
65
|
+
Parameters: { startDate, endDate }, // INVALID!
|
|
66
|
+
GroupBy: 'Status' // INVALID!
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// ✅ CORRECT - Use ExtraFilter for WHERE clauses:
|
|
70
|
+
await utilities.rv.RunView({
|
|
71
|
+
EntityName: 'MJ: AI Prompt Runs',
|
|
72
|
+
ExtraFilter: \`RunAt >= '\${startDate.toISOString()}' AND RunAt <= '\${endDate.toISOString()}'\`,
|
|
73
|
+
OrderBy: 'RunAt DESC',
|
|
74
|
+
Fields: ['RunAt', 'Status', 'Success']
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ✅ For aggregations, use RunQuery with a pre-defined query:
|
|
78
|
+
await utilities.rq.RunQuery({
|
|
79
|
+
QueryName: 'Prompt Run Summary',
|
|
80
|
+
Parameters: { startDate, endDate } // Parameters ARE valid for RunQuery
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Valid RunView properties:
|
|
84
|
+
// - EntityName (required)
|
|
85
|
+
// - ExtraFilter, OrderBy, Fields, MaxRows, StartRow, ResultType (optional)
|
|
86
|
+
|
|
87
|
+
// Valid RunQuery properties:
|
|
88
|
+
// - QueryName (required)
|
|
89
|
+
// - CategoryPath, CategoryID, Parameters (optional)`,
|
|
90
|
+
};
|
|
91
|
+
// ── Callee matching ──────────────────────────────────────────────────
|
|
92
|
+
function isRunQueryCallee(callee) {
|
|
93
|
+
return (t.isMemberExpression(callee) &&
|
|
94
|
+
t.isMemberExpression(callee.object) &&
|
|
95
|
+
t.isIdentifier(callee.object.object) &&
|
|
96
|
+
callee.object.object.name === 'utilities' &&
|
|
97
|
+
t.isIdentifier(callee.object.property) &&
|
|
98
|
+
callee.object.property.name === 'rq' &&
|
|
99
|
+
t.isIdentifier(callee.property) &&
|
|
100
|
+
callee.property.name === 'RunQuery');
|
|
101
|
+
}
|
|
102
|
+
// ── useState init collector ──────────────────────────────────────────
|
|
103
|
+
function collectUseStateInits(ast) {
|
|
104
|
+
const stateInits = new Map();
|
|
105
|
+
traverse(ast, {
|
|
106
|
+
VariableDeclarator(path) {
|
|
107
|
+
if (!t.isArrayPattern(path.node.id))
|
|
108
|
+
return;
|
|
109
|
+
const init = path.node.init;
|
|
110
|
+
if (!init || !t.isCallExpression(init) || !t.isIdentifier(init.callee) || init.callee.name !== 'useState')
|
|
111
|
+
return;
|
|
112
|
+
const firstEl = path.node.id.elements[0];
|
|
113
|
+
if (!t.isIdentifier(firstEl))
|
|
114
|
+
return;
|
|
115
|
+
if (init.arguments.length > 0) {
|
|
116
|
+
const arg = init.arguments[0];
|
|
117
|
+
const category = getLiteralCategory(arg);
|
|
118
|
+
if (category && category !== 'null') {
|
|
119
|
+
let description;
|
|
120
|
+
if (t.isNumericLiteral(arg))
|
|
121
|
+
description = String(arg.value);
|
|
122
|
+
else if (t.isStringLiteral(arg))
|
|
123
|
+
description = `"${arg.value}"`;
|
|
124
|
+
else if (t.isBooleanLiteral(arg))
|
|
125
|
+
description = String(arg.value);
|
|
126
|
+
else
|
|
127
|
+
description = category;
|
|
128
|
+
stateInits.set(firstEl.name, { category, description });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
noScope: true,
|
|
133
|
+
});
|
|
134
|
+
return stateInits;
|
|
135
|
+
}
|
|
136
|
+
function getLiteralCategory(node) {
|
|
137
|
+
if (t.isNumericLiteral(node))
|
|
138
|
+
return 'number';
|
|
139
|
+
if (t.isStringLiteral(node))
|
|
140
|
+
return 'string';
|
|
141
|
+
if (t.isBooleanLiteral(node))
|
|
142
|
+
return 'boolean';
|
|
143
|
+
if (t.isNullLiteral(node))
|
|
144
|
+
return 'null';
|
|
145
|
+
if (t.isTemplateLiteral(node))
|
|
146
|
+
return 'string';
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
function expectedJsCategory(paramType) {
|
|
150
|
+
if (!paramType)
|
|
151
|
+
return null;
|
|
152
|
+
const lower = paramType.toLowerCase().replace(/\(.*\)/, '').trim();
|
|
153
|
+
const numericTypes = new Set([
|
|
154
|
+
'int', 'bigint', 'smallint', 'tinyint', 'decimal', 'numeric',
|
|
155
|
+
'float', 'real', 'money', 'smallmoney', 'number',
|
|
156
|
+
]);
|
|
157
|
+
const stringTypes = new Set([
|
|
158
|
+
'nvarchar', 'varchar', 'char', 'nchar', 'text', 'ntext',
|
|
159
|
+
'uniqueidentifier', 'string',
|
|
160
|
+
]);
|
|
161
|
+
const boolTypes = new Set(['bit', 'boolean']);
|
|
162
|
+
if (numericTypes.has(lower))
|
|
163
|
+
return 'number';
|
|
164
|
+
if (stringTypes.has(lower))
|
|
165
|
+
return 'string';
|
|
166
|
+
if (boolTypes.has(lower))
|
|
167
|
+
return 'boolean';
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
// ── Invalid-property message builder ─────────────────────────────────
|
|
171
|
+
function buildInvalidRunQueryPropertyMessage(propName) {
|
|
172
|
+
switch (propName) {
|
|
173
|
+
case 'ExtraFilter':
|
|
174
|
+
return `RunQuery does not support 'ExtraFilter'. WHERE clauses should be in the pre-defined query or passed as Parameters.`;
|
|
175
|
+
case 'Fields':
|
|
176
|
+
return `RunQuery does not support 'Fields'. The query definition determines returned fields.`;
|
|
177
|
+
case 'OrderBy':
|
|
178
|
+
return `RunQuery does not support 'OrderBy'. ORDER BY should be in the query definition.`;
|
|
179
|
+
default:
|
|
180
|
+
return `Invalid property '${propName}' on RunQuery. Valid properties: ${Array.from(VALID_RUNQUERY_PROPS).join(', ')}`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// ── Property type validation ─────────────────────────────────────────
|
|
184
|
+
function validateRunQueryPropertyType(propName, value, prop, violations) {
|
|
185
|
+
if (propName === 'QueryID' || propName === 'QueryName' || propName === 'CategoryID' || propName === 'CategoryPath') {
|
|
186
|
+
if (!isStringLike(value)) {
|
|
187
|
+
const exampleMap = {
|
|
188
|
+
QueryID: `"550e8400-e29b-41d4-a716-446655440000"`,
|
|
189
|
+
QueryName: `"Sales by Region"`,
|
|
190
|
+
CategoryID: `"123e4567-e89b-12d3-a456-426614174000"`,
|
|
191
|
+
CategoryPath: `"/Reports/Sales/"`,
|
|
192
|
+
};
|
|
193
|
+
violations.push({
|
|
194
|
+
rule: RULE_NAME,
|
|
195
|
+
severity: 'critical',
|
|
196
|
+
line: prop.loc?.start.line || 0,
|
|
197
|
+
column: prop.loc?.start.column || 0,
|
|
198
|
+
message: `RunQuery property '${propName}' must be a string. Example: ${propName}: ${exampleMap[propName] || '""'}`,
|
|
199
|
+
code: `${propName}: ${value.type === 'ObjectExpression' ? '{...}' : value.type === 'ArrayExpression' ? '[...]' : '...'}`,
|
|
200
|
+
suggestion: DEFAULT_SUGGESTION,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else if (propName === 'Parameters') {
|
|
205
|
+
if (!isObjectLike(value)) {
|
|
206
|
+
violations.push({
|
|
207
|
+
rule: RULE_NAME,
|
|
208
|
+
severity: 'critical',
|
|
209
|
+
line: prop.loc?.start.line || 0,
|
|
210
|
+
column: prop.loc?.start.column || 0,
|
|
211
|
+
message: `RunQuery property 'Parameters' must be an object containing key-value pairs. Example: Parameters: { startDate: '2024-01-01', status: 'Active' }`,
|
|
212
|
+
code: `Parameters: ${t.isArrayExpression(value) ? '[...]' : t.isStringLiteral(value) ? '"..."' : '...'}`,
|
|
213
|
+
suggestion: DEFAULT_SUGGESTION,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else if (propName === 'MaxRows' || propName === 'StartRow') {
|
|
218
|
+
if (!isNumberLike(value)) {
|
|
219
|
+
violations.push({
|
|
220
|
+
rule: RULE_NAME,
|
|
221
|
+
severity: 'critical',
|
|
222
|
+
line: prop.loc?.start.line || 0,
|
|
223
|
+
column: prop.loc?.start.column || 0,
|
|
224
|
+
message: `RunQuery property '${propName}' must be a number. Example: ${propName}: ${propName === 'MaxRows' ? '100' : '0'}`,
|
|
225
|
+
code: `${propName}: ${value.type === 'StringLiteral' ? '"..."' : value.type === 'ObjectExpression' ? '{...}' : '...'}`,
|
|
226
|
+
suggestion: DEFAULT_SUGGESTION,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// ── SQL injection detection ──────────────────────────────────────────
|
|
232
|
+
function detectSQLInjection(value, path, violations, knownQueryNames) {
|
|
233
|
+
if (t.isStringLiteral(value)) {
|
|
234
|
+
const queryName = value.value;
|
|
235
|
+
// Skip SQL check if the query name is a known registered query from the spec
|
|
236
|
+
if (knownQueryNames?.has(queryName))
|
|
237
|
+
return;
|
|
238
|
+
// Check for structural SQL patterns (require multiple keywords in combination
|
|
239
|
+
// to avoid false positives on domain terms like "Join Year", "Revenue From Events")
|
|
240
|
+
const looksLikeSQL = SQL_PATTERNS.some((pattern) => pattern.test(queryName));
|
|
241
|
+
if (looksLikeSQL) {
|
|
242
|
+
violations.push({
|
|
243
|
+
rule: RULE_NAME,
|
|
244
|
+
severity: 'critical',
|
|
245
|
+
line: value.loc?.start.line || 0,
|
|
246
|
+
column: value.loc?.start.column || 0,
|
|
247
|
+
message: `RunQuery cannot accept SQL statements. QueryName must be a registered query name, not SQL: "${queryName.substring(0, 50)}..."`,
|
|
248
|
+
code: value.value.substring(0, 100),
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else if (t.isIdentifier(value) || t.isTemplateLiteral(value)) {
|
|
253
|
+
violations.push({
|
|
254
|
+
rule: RULE_NAME,
|
|
255
|
+
severity: 'medium',
|
|
256
|
+
line: value.loc?.start.line || 0,
|
|
257
|
+
column: value.loc?.start.column || 0,
|
|
258
|
+
message: `Dynamic QueryName detected. Ensure this is a query name, not a SQL statement.`,
|
|
259
|
+
code: path.toString().substring(0, 100),
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// ── Query existence validation ───────────────────────────────────────
|
|
264
|
+
function validateQueryExistence(queryName, componentSpec, path, violations) {
|
|
265
|
+
if (!componentSpec?.dataRequirements?.queries)
|
|
266
|
+
return;
|
|
267
|
+
const queryExists = componentSpec.dataRequirements.queries.some((q) => q.name === queryName);
|
|
268
|
+
if (!queryExists) {
|
|
269
|
+
const availableQueries = componentSpec.dataRequirements.queries.map((q) => q.name).join(', ');
|
|
270
|
+
violations.push({
|
|
271
|
+
rule: RULE_NAME,
|
|
272
|
+
severity: 'high',
|
|
273
|
+
line: path.node.loc?.start.line || 0,
|
|
274
|
+
column: path.node.loc?.start.column || 0,
|
|
275
|
+
message: `Query '${queryName}' not found in component spec. Available queries: ${availableQueries || 'none'}`,
|
|
276
|
+
code: `QueryName: '${componentSpec.dataRequirements.queries[0]?.name || 'QueryNameFromSpec'}'`,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// ── CategoryPath validation ──────────────────────────────────────────
|
|
281
|
+
function validateCategoryPath(queryName, hasCategoryPath, queryNameProp, path, componentSpec, violations) {
|
|
282
|
+
if (!componentSpec?.dataRequirements?.queries || hasCategoryPath)
|
|
283
|
+
return;
|
|
284
|
+
const specQuery = componentSpec.dataRequirements.queries.find((q) => q.name === queryName);
|
|
285
|
+
if (!specQuery?.categoryPath || specQuery.categoryPath.trim().length === 0)
|
|
286
|
+
return;
|
|
287
|
+
const expectedCategoryPath = specQuery.categoryPath;
|
|
288
|
+
violations.push({
|
|
289
|
+
rule: RULE_NAME,
|
|
290
|
+
severity: 'critical',
|
|
291
|
+
line: queryNameProp?.loc?.start.line || path.node.loc?.start.line || 0,
|
|
292
|
+
column: queryNameProp?.loc?.start.column || path.node.loc?.start.column || 0,
|
|
293
|
+
message: `RunQuery with QueryName '${queryName}' is missing required CategoryPath parameter. Queries are uniquely identified by both QueryName and CategoryPath together. Without CategoryPath, RunQuery may find a different query with the same name, causing collisions and unintended behavior.`,
|
|
294
|
+
code: `RunQuery({ QueryName: '${queryName}' }) // Missing: CategoryPath`,
|
|
295
|
+
suggestion: {
|
|
296
|
+
text: `Add CategoryPath property to uniquely identify the query. The CategoryPath should match what's defined in your dataRequirements.queries[].categoryPath`,
|
|
297
|
+
example: `await utilities.rq.RunQuery({
|
|
298
|
+
QueryName: '${queryName}',
|
|
299
|
+
CategoryPath: '${expectedCategoryPath}', // Required: ensures correct query is used
|
|
300
|
+
Parameters: {
|
|
301
|
+
// Your query parameters here
|
|
302
|
+
}
|
|
303
|
+
})`,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
// ── Parameters: array format ─────────────────────────────────────────
|
|
308
|
+
function validateParametersArray(paramValue, parametersNode, specQuery, violations) {
|
|
309
|
+
const arrayElements = paramValue.elements.filter((e) => t.isObjectExpression(e));
|
|
310
|
+
const paramPairs = [];
|
|
311
|
+
let isNameValueFormat = true;
|
|
312
|
+
for (const elem of arrayElements) {
|
|
313
|
+
let name = null;
|
|
314
|
+
let value = null;
|
|
315
|
+
for (const prop of elem.properties) {
|
|
316
|
+
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
|
|
317
|
+
const propName = prop.key.name.toLowerCase();
|
|
318
|
+
if (propName === 'name' || propName === 'fieldname') {
|
|
319
|
+
if (t.isStringLiteral(prop.value))
|
|
320
|
+
name = prop.value.value;
|
|
321
|
+
else if (t.isIdentifier(prop.value))
|
|
322
|
+
name = prop.value.name;
|
|
323
|
+
}
|
|
324
|
+
else if (propName === 'value') {
|
|
325
|
+
if (t.isStringLiteral(prop.value))
|
|
326
|
+
value = `'${prop.value.value}'`;
|
|
327
|
+
else if (t.isNumericLiteral(prop.value))
|
|
328
|
+
value = prop.value.value;
|
|
329
|
+
else if (t.isBooleanLiteral(prop.value))
|
|
330
|
+
value = prop.value.value;
|
|
331
|
+
else if (t.isIdentifier(prop.value))
|
|
332
|
+
value = prop.value.name;
|
|
333
|
+
else
|
|
334
|
+
value = '/* value */';
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (name && value !== null) {
|
|
339
|
+
paramPairs.push({ name, value });
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
isNameValueFormat = false;
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
let fixMessage;
|
|
347
|
+
let fixCode;
|
|
348
|
+
if (isNameValueFormat && paramPairs.length > 0) {
|
|
349
|
+
const objProps = paramPairs.map((p) => ` ${p.name}: ${p.value}`).join(',\n');
|
|
350
|
+
fixCode = `Parameters: {\n${objProps}\n}`;
|
|
351
|
+
if (specQuery?.parameters) {
|
|
352
|
+
const specParamNames = specQuery.parameters.map((p) => p.name);
|
|
353
|
+
const providedNames = paramPairs.map((p) => p.name);
|
|
354
|
+
const missing = specParamNames.filter((n) => !providedNames.includes(n));
|
|
355
|
+
const extra = providedNames.filter((n) => !specParamNames.includes(n));
|
|
356
|
+
if (missing.length > 0 || extra.length > 0) {
|
|
357
|
+
fixMessage = `RunQuery Parameters must be object, not array. `;
|
|
358
|
+
if (missing.length > 0)
|
|
359
|
+
fixMessage += `Missing required: ${missing.join(', ')}. `;
|
|
360
|
+
if (extra.length > 0)
|
|
361
|
+
fixMessage += `Unknown params: ${extra.join(', ')}. `;
|
|
362
|
+
fixMessage += `Expected params from spec: ${specParamNames.join(', ')}`;
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
fixMessage = `RunQuery Parameters must be object with key-value pairs, not array. Auto-fix: convert [{Name,Value}] to object format`;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
fixMessage = `RunQuery Parameters must be object with key-value pairs, not array of {Name/Value} objects`;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
if (specQuery?.parameters && specQuery.parameters.length > 0) {
|
|
374
|
+
const exampleParams = specQuery.parameters
|
|
375
|
+
.slice(0, 3)
|
|
376
|
+
.map((p) => ` ${p.name}: '${p.testValue || 'value'}'`)
|
|
377
|
+
.join(',\n');
|
|
378
|
+
fixCode = `Parameters: {\n${exampleParams}\n}`;
|
|
379
|
+
fixMessage = `RunQuery Parameters must be object. Expected params: ${specQuery.parameters.map((p) => p.name).join(', ')}`;
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
fixCode = `Parameters: {\n paramName1: 'value1',\n paramName2: 'value2'\n}`;
|
|
383
|
+
fixMessage = `RunQuery Parameters must be object with key-value pairs, not array`;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
violations.push({
|
|
387
|
+
rule: RULE_NAME,
|
|
388
|
+
severity: 'critical',
|
|
389
|
+
line: parametersNode.loc?.start.line || 0,
|
|
390
|
+
column: parametersNode.loc?.start.column || 0,
|
|
391
|
+
message: fixMessage,
|
|
392
|
+
code: fixCode,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
// ── Parameters: object format (spec validation) ──────────────────────
|
|
396
|
+
function validateParametersObject(paramValue, parametersNode, queryName, specQuery, violations) {
|
|
397
|
+
if (!specQuery.parameters)
|
|
398
|
+
return;
|
|
399
|
+
const providedParamsMap = new Map();
|
|
400
|
+
for (const prop of paramValue.properties) {
|
|
401
|
+
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
|
|
402
|
+
providedParamsMap.set(prop.key.name.toLowerCase(), prop.key.name);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const requiredParams = specQuery.parameters.filter((p) => {
|
|
406
|
+
return p.isRequired === true || p.value === '@runtime';
|
|
407
|
+
});
|
|
408
|
+
const specParamNames = specQuery.parameters.map((p) => p.name);
|
|
409
|
+
const specParamNamesLower = specParamNames.map((n) => n.toLowerCase());
|
|
410
|
+
const missing = requiredParams
|
|
411
|
+
.map((p) => p.name)
|
|
412
|
+
.filter((n) => !providedParamsMap.has(n.toLowerCase()));
|
|
413
|
+
const extra = Array.from(providedParamsMap.values()).filter((providedName) => !specParamNamesLower.includes(providedName.toLowerCase()));
|
|
414
|
+
if (missing.length > 0 || extra.length > 0) {
|
|
415
|
+
let message = `Query '${queryName}' parameter mismatch. `;
|
|
416
|
+
if (missing.length > 0)
|
|
417
|
+
message += `Missing: ${missing.join(', ')}. `;
|
|
418
|
+
if (extra.length > 0)
|
|
419
|
+
message += `Unknown: ${extra.join(', ')}. `;
|
|
420
|
+
const correctParams = specQuery.parameters
|
|
421
|
+
.map((p) => {
|
|
422
|
+
const providedName = providedParamsMap.get(p.name.toLowerCase());
|
|
423
|
+
if (providedName) {
|
|
424
|
+
const existingProp = paramValue.properties.find((prop) => t.isObjectProperty(prop) && t.isIdentifier(prop.key) && prop.key.name.toLowerCase() === p.name.toLowerCase());
|
|
425
|
+
if (existingProp && t.isStringLiteral(existingProp.value))
|
|
426
|
+
return ` ${p.name}: '${existingProp.value.value}'`;
|
|
427
|
+
if (existingProp && t.isNumericLiteral(existingProp.value))
|
|
428
|
+
return ` ${p.name}: ${existingProp.value.value}`;
|
|
429
|
+
if (existingProp && t.isIdentifier(existingProp.value))
|
|
430
|
+
return ` ${p.name}: ${existingProp.value.name}`;
|
|
431
|
+
}
|
|
432
|
+
return ` ${p.name}: '${p.testValue || 'value'}'`;
|
|
433
|
+
})
|
|
434
|
+
.join(',\n');
|
|
435
|
+
violations.push({
|
|
436
|
+
rule: RULE_NAME,
|
|
437
|
+
severity: 'high',
|
|
438
|
+
line: parametersNode.loc?.start.line || 0,
|
|
439
|
+
column: parametersNode.loc?.start.column || 0,
|
|
440
|
+
message: message + `Expected: {${specParamNames.join(', ')}}`,
|
|
441
|
+
code: `Parameters: {\n${correctParams}\n}`,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// ── Parameters: variable reference (TypeContext) ─────────────────────
|
|
446
|
+
function validateParametersVariable(paramValue, parametersNode, queryName, specQuery, typeContext, violations) {
|
|
447
|
+
if (!typeContext || !specQuery.parameters)
|
|
448
|
+
return;
|
|
449
|
+
const varType = typeContext.getVariableType(paramValue.name);
|
|
450
|
+
if (varType?.type !== 'object' || !varType.fields)
|
|
451
|
+
return;
|
|
452
|
+
const providedParamsLower = new Map();
|
|
453
|
+
for (const [fieldName] of varType.fields) {
|
|
454
|
+
providedParamsLower.set(fieldName.toLowerCase(), fieldName);
|
|
455
|
+
}
|
|
456
|
+
const specParamNames = specQuery.parameters.map((p) => p.name);
|
|
457
|
+
const specParamNamesLower = specParamNames.map((n) => n.toLowerCase());
|
|
458
|
+
const extra = Array.from(providedParamsLower.values()).filter((name) => !specParamNamesLower.includes(name.toLowerCase()));
|
|
459
|
+
if (extra.length > 0) {
|
|
460
|
+
violations.push({
|
|
461
|
+
rule: RULE_NAME,
|
|
462
|
+
severity: 'high',
|
|
463
|
+
line: parametersNode.loc?.start.line || 0,
|
|
464
|
+
column: parametersNode.loc?.start.column || 0,
|
|
465
|
+
message: `Query '${queryName}' has unknown parameters: ${extra.join(', ')}. Expected: {${specParamNames.join(', ')}}`,
|
|
466
|
+
code: `Parameters: ${paramValue.name}`,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
// ── Parameters: other invalid type ───────────────────────────────────
|
|
471
|
+
function validateParametersOtherType(parametersNode, specQuery, violations) {
|
|
472
|
+
let fixCode;
|
|
473
|
+
let message;
|
|
474
|
+
if (specQuery?.parameters && specQuery.parameters.length > 0) {
|
|
475
|
+
const exampleParams = specQuery.parameters.map((p) => ` ${p.name}: '${p.testValue || 'value'}'`).join(',\n');
|
|
476
|
+
fixCode = `Parameters: {\n${exampleParams}\n}`;
|
|
477
|
+
message = `RunQuery Parameters must be object. Expected params from spec: ${specQuery.parameters.map((p) => p.name).join(', ')}`;
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
fixCode = `Parameters: {\n paramName: 'value'\n}`;
|
|
481
|
+
message = `RunQuery Parameters must be object with key-value pairs`;
|
|
482
|
+
}
|
|
483
|
+
violations.push({
|
|
484
|
+
rule: RULE_NAME,
|
|
485
|
+
severity: 'critical',
|
|
486
|
+
line: parametersNode.loc?.start.line || 0,
|
|
487
|
+
column: parametersNode.loc?.start.column || 0,
|
|
488
|
+
message,
|
|
489
|
+
code: fixCode,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
// ── Parameter value type validation ──────────────────────────────────
|
|
493
|
+
function validateParameterValueTypes(parametersNode, queryName, componentSpec, violations) {
|
|
494
|
+
const querySpec = componentSpec.dataRequirements?.queries?.find((q) => q.name === queryName);
|
|
495
|
+
if (!querySpec?.parameters)
|
|
496
|
+
return;
|
|
497
|
+
// Build type map
|
|
498
|
+
const paramTypes = new Map();
|
|
499
|
+
for (const param of querySpec.parameters) {
|
|
500
|
+
const extParam = param;
|
|
501
|
+
if (extParam.type) {
|
|
502
|
+
paramTypes.set(param.name.toLowerCase(), {
|
|
503
|
+
type: mapSQLTypeToJSType(extParam.type),
|
|
504
|
+
sqlType: extParam.type,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (paramTypes.size === 0)
|
|
509
|
+
return;
|
|
510
|
+
for (const prop of parametersNode.properties) {
|
|
511
|
+
if (!t.isObjectProperty(prop) || !t.isIdentifier(prop.key))
|
|
512
|
+
continue;
|
|
513
|
+
const paramName = prop.key.name;
|
|
514
|
+
const paramTypeInfo = paramTypes.get(paramName.toLowerCase());
|
|
515
|
+
if (!paramTypeInfo)
|
|
516
|
+
continue;
|
|
517
|
+
const expectedType = paramTypeInfo.type;
|
|
518
|
+
let actualType = null;
|
|
519
|
+
let valueDesc = '';
|
|
520
|
+
if (t.isStringLiteral(prop.value)) {
|
|
521
|
+
actualType = 'string';
|
|
522
|
+
valueDesc = `'${prop.value.value}'`;
|
|
523
|
+
}
|
|
524
|
+
else if (t.isNumericLiteral(prop.value)) {
|
|
525
|
+
actualType = 'number';
|
|
526
|
+
valueDesc = String(prop.value.value);
|
|
527
|
+
}
|
|
528
|
+
else if (t.isBooleanLiteral(prop.value)) {
|
|
529
|
+
actualType = 'boolean';
|
|
530
|
+
valueDesc = String(prop.value.value);
|
|
531
|
+
}
|
|
532
|
+
else if (t.isNullLiteral(prop.value)) {
|
|
533
|
+
actualType = 'null';
|
|
534
|
+
valueDesc = 'null';
|
|
535
|
+
}
|
|
536
|
+
else if (t.isIdentifier(prop.value)) {
|
|
537
|
+
continue;
|
|
538
|
+
} // Variable - skip
|
|
539
|
+
else if (t.isTemplateLiteral(prop.value)) {
|
|
540
|
+
actualType = 'string';
|
|
541
|
+
valueDesc = 'template string';
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
continue;
|
|
545
|
+
} // Complex expression - skip
|
|
546
|
+
if (actualType && actualType !== expectedType) {
|
|
547
|
+
if (actualType === 'null')
|
|
548
|
+
continue; // Allow null for nullable params
|
|
549
|
+
let suggestion = '';
|
|
550
|
+
if (expectedType === 'number' && actualType === 'string') {
|
|
551
|
+
if (t.isStringLiteral(prop.value) && !isNaN(Number(prop.value.value))) {
|
|
552
|
+
suggestion = `Use ${paramName}: ${prop.value.value} (without quotes)`;
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
suggestion = `Use a numeric value`;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
else if (expectedType === 'boolean' && actualType === 'string') {
|
|
559
|
+
if (t.isStringLiteral(prop.value)) {
|
|
560
|
+
const val = prop.value.value.toLowerCase();
|
|
561
|
+
if (val === 'true' || val === 'false') {
|
|
562
|
+
suggestion = `Use ${paramName}: ${val} (without quotes)`;
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
suggestion = `Use ${paramName}: true or ${paramName}: false`;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
else if (expectedType === 'string' && actualType === 'number') {
|
|
570
|
+
suggestion = `Use ${paramName}: '${valueDesc}'`;
|
|
571
|
+
}
|
|
572
|
+
violations.push({
|
|
573
|
+
rule: RULE_NAME,
|
|
574
|
+
severity: 'high',
|
|
575
|
+
line: prop.loc?.start.line || 0,
|
|
576
|
+
column: prop.loc?.start.column || 0,
|
|
577
|
+
message: `Parameter "${paramName}" has wrong type. Expected ${expectedType} (${paramTypeInfo.sqlType}), got ${actualType} (${valueDesc}).${suggestion ? ' ' + suggestion : ''}`,
|
|
578
|
+
code: suggestion || `${paramName}: <${expectedType} value>`,
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
// ── Null/array/useState per-param checks ─────────────────────────────
|
|
584
|
+
function validateIndividualParamValues(parametersNode, queryName, componentSpec, stateInits, violations) {
|
|
585
|
+
const querySpec = componentSpec.dataRequirements?.queries?.find((q) => q.name === queryName);
|
|
586
|
+
if (!querySpec?.parameters)
|
|
587
|
+
return;
|
|
588
|
+
const specParamMap = new Map();
|
|
589
|
+
for (const p of querySpec.parameters) {
|
|
590
|
+
specParamMap.set(p.name.toLowerCase(), p);
|
|
591
|
+
}
|
|
592
|
+
for (const prop of parametersNode.properties) {
|
|
593
|
+
if (!t.isObjectProperty(prop) || !t.isIdentifier(prop.key))
|
|
594
|
+
continue;
|
|
595
|
+
const paramName = prop.key.name;
|
|
596
|
+
const specParam = specParamMap.get(paramName.toLowerCase());
|
|
597
|
+
if (!specParam)
|
|
598
|
+
continue;
|
|
599
|
+
const value = prop.value;
|
|
600
|
+
const isRequired = specParam.isRequired || specParam.value === '@runtime';
|
|
601
|
+
// Check: null literal on required parameter
|
|
602
|
+
if (t.isNullLiteral(value) && isRequired) {
|
|
603
|
+
violations.push({
|
|
604
|
+
rule: RULE_NAME,
|
|
605
|
+
severity: 'high',
|
|
606
|
+
line: prop.loc?.start.line ?? 0,
|
|
607
|
+
column: prop.loc?.start.column ?? 0,
|
|
608
|
+
message: `Required query parameter "${paramName}" is set to null. This will cause the query to fail or return unexpected results.`,
|
|
609
|
+
code: `${paramName}: null`,
|
|
610
|
+
suggestion: {
|
|
611
|
+
text: `Provide a valid value for "${paramName}" or add a null guard before calling RunQuery`,
|
|
612
|
+
example: `${paramName}: ${specParam.testValue ? `'${specParam.testValue}'` : "'value'"}`,
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
// Check: array expression for scalar parameter
|
|
618
|
+
if (t.isArrayExpression(value)) {
|
|
619
|
+
const paramType = specParam.type?.toLowerCase().replace(/\(.*\)/, '').trim();
|
|
620
|
+
if (!paramType || SCALAR_SQL_TYPES.has(paramType)) {
|
|
621
|
+
violations.push({
|
|
622
|
+
rule: RULE_NAME,
|
|
623
|
+
severity: 'high',
|
|
624
|
+
line: prop.loc?.start.line ?? 0,
|
|
625
|
+
column: prop.loc?.start.column ?? 0,
|
|
626
|
+
message: `Query parameter "${paramName}" expects a scalar value (type: ${specParam.type ?? 'scalar'}) but received an array. Pass a single value instead.`,
|
|
627
|
+
code: `${paramName}: [...]`,
|
|
628
|
+
suggestion: {
|
|
629
|
+
text: `Pass a single value instead of an array`,
|
|
630
|
+
example: `${paramName}: ${specParam.testValue ? `'${specParam.testValue}'` : "'value'"}`,
|
|
631
|
+
},
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
// Check: useState variable with mismatched init type
|
|
637
|
+
if (t.isIdentifier(value)) {
|
|
638
|
+
const stateInfo = stateInits.get(value.name);
|
|
639
|
+
if (!stateInfo)
|
|
640
|
+
continue;
|
|
641
|
+
const expectedCategory = expectedJsCategory(specParam.type);
|
|
642
|
+
if (!expectedCategory)
|
|
643
|
+
continue;
|
|
644
|
+
if (stateInfo.category !== expectedCategory) {
|
|
645
|
+
violations.push({
|
|
646
|
+
rule: RULE_NAME,
|
|
647
|
+
severity: 'high',
|
|
648
|
+
line: prop.loc?.start.line ?? 0,
|
|
649
|
+
column: prop.loc?.start.column ?? 0,
|
|
650
|
+
message: `Query parameter "${paramName}" expects type "${specParam.type}" (${expectedCategory}) but state variable "${value.name}" is initialized as ${stateInfo.category} (${stateInfo.description}). This type mismatch may cause the query to fail.`,
|
|
651
|
+
code: `${paramName}: ${value.name}`,
|
|
652
|
+
suggestion: {
|
|
653
|
+
text: `Initialize the state variable with a ${expectedCategory} value, or convert before passing`,
|
|
654
|
+
example: expectedCategory === 'number'
|
|
655
|
+
? `useState(0)`
|
|
656
|
+
: expectedCategory === 'string'
|
|
657
|
+
? `useState('')`
|
|
658
|
+
: `useState(false)`,
|
|
659
|
+
},
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
// ── Missing required Parameters property ─────────────────────────────
|
|
666
|
+
function validateMissingParametersProperty(queryName, specQuery, path, violations) {
|
|
667
|
+
if (!specQuery?.parameters || specQuery.parameters.length === 0)
|
|
668
|
+
return;
|
|
669
|
+
const requiredParams = specQuery.parameters.filter((p) => {
|
|
670
|
+
return p.isRequired === true || p.value === '@runtime';
|
|
671
|
+
});
|
|
672
|
+
if (requiredParams.length > 0) {
|
|
673
|
+
const paramNames = requiredParams.map((p) => p.name).join(', ');
|
|
674
|
+
const exampleParams = requiredParams.map((p) => ` ${p.name}: ${p.testValue ? `'${p.testValue}'` : "'value'"}`).join(',\n');
|
|
675
|
+
violations.push({
|
|
676
|
+
rule: RULE_NAME,
|
|
677
|
+
severity: 'high',
|
|
678
|
+
line: path.node.loc?.start.line || 0,
|
|
679
|
+
column: path.node.loc?.start.column || 0,
|
|
680
|
+
message: `Query '${queryName}' requires parameters but RunQuery call is missing 'Parameters' property. Required: ${paramNames}`,
|
|
681
|
+
code: `Parameters: {\n${exampleParams}\n}`,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
// ── Main Rule ────────────────────────────────────────────────────────
|
|
686
|
+
let RunQueryCallValidationRule = class RunQueryCallValidationRule extends BaseLintRule {
|
|
687
|
+
get Name() { return 'runquery-call-validation'; }
|
|
688
|
+
get AppliesTo() { return 'all'; }
|
|
689
|
+
Test(ast, _componentName, componentSpec, _options, typeContext) {
|
|
690
|
+
const violations = [];
|
|
691
|
+
// Pre-collect useState initializers for type checking
|
|
692
|
+
const stateInits = collectUseStateInits(ast);
|
|
693
|
+
traverse(ast, {
|
|
694
|
+
CallExpression(path) {
|
|
695
|
+
if (!isRunQueryCallee(path.node.callee))
|
|
696
|
+
return;
|
|
697
|
+
// A. Argument structure checks
|
|
698
|
+
if (!path.node.arguments[0]) {
|
|
699
|
+
violations.push({
|
|
700
|
+
rule: RULE_NAME,
|
|
701
|
+
severity: 'critical',
|
|
702
|
+
line: path.node.loc?.start.line || 0,
|
|
703
|
+
column: path.node.loc?.start.column || 0,
|
|
704
|
+
message: `RunQuery requires a RunQueryParams object as the first parameter.
|
|
705
|
+
Use: RunQuery({
|
|
706
|
+
QueryName: 'YourQuery', // Or use QueryID: 'uuid'
|
|
707
|
+
CategoryPath: 'Category/Subcategory', // Optional. Used when QueryName is provided to provide a better filter
|
|
708
|
+
Parameters: { // Optional query parameters
|
|
709
|
+
param1: 'value1'
|
|
710
|
+
},
|
|
711
|
+
StartRow: 0, // Optional offset (0-based)
|
|
712
|
+
MaxRows: 100 // Optional limit
|
|
713
|
+
})`,
|
|
714
|
+
code: `RunQuery()`,
|
|
715
|
+
suggestion: DEFAULT_SUGGESTION,
|
|
716
|
+
});
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
if (t.isIdentifier(path.node.arguments[0]))
|
|
720
|
+
return; // Variable — skip
|
|
721
|
+
if (!t.isObjectExpression(path.node.arguments[0])) {
|
|
722
|
+
const argType = t.isStringLiteral(path.node.arguments[0]) ? 'string' : 'non-object';
|
|
723
|
+
violations.push({
|
|
724
|
+
rule: RULE_NAME,
|
|
725
|
+
severity: 'critical',
|
|
726
|
+
line: path.node.arguments[0].loc?.start.line || 0,
|
|
727
|
+
column: path.node.arguments[0].loc?.start.column || 0,
|
|
728
|
+
message: `RunQuery expects a RunQueryParams object, not a ${argType}.
|
|
729
|
+
Use: RunQuery({
|
|
730
|
+
QueryName: 'YourQuery', // Or use QueryID: 'uuid'
|
|
731
|
+
CategoryPath: 'Category/Subcategory', // Optional. Used when QueryName is provided to provide a better filter
|
|
732
|
+
Parameters: { // Optional query parameters
|
|
733
|
+
startDate: '2024-01-01',
|
|
734
|
+
endDate: '2024-12-31'
|
|
735
|
+
},
|
|
736
|
+
StartRow: 0, // Optional offset (0-based)
|
|
737
|
+
MaxRows: 100 // Optional limit
|
|
738
|
+
})
|
|
739
|
+
Valid properties: QueryID, QueryName, CategoryID, CategoryPath, Parameters, MaxRows, StartRow, ForceAuditLog, AuditLogDescription`,
|
|
740
|
+
code: path.toString().substring(0, 100),
|
|
741
|
+
suggestion: DEFAULT_SUGGESTION,
|
|
742
|
+
});
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
const config = path.node.arguments[0];
|
|
746
|
+
// B. Extract properties and validate
|
|
747
|
+
let hasQueryID = false;
|
|
748
|
+
let hasQueryName = false;
|
|
749
|
+
let hasCategoryPath = false;
|
|
750
|
+
let queryName = null;
|
|
751
|
+
let parametersNode = null;
|
|
752
|
+
let queryNameProp;
|
|
753
|
+
const foundProps = [];
|
|
754
|
+
for (const prop of config.properties) {
|
|
755
|
+
if (!t.isObjectProperty(prop) || !t.isIdentifier(prop.key))
|
|
756
|
+
continue;
|
|
757
|
+
const propName = prop.key.name;
|
|
758
|
+
foundProps.push(propName);
|
|
759
|
+
if (propName === 'QueryID')
|
|
760
|
+
hasQueryID = true;
|
|
761
|
+
if (propName === 'QueryName') {
|
|
762
|
+
hasQueryName = true;
|
|
763
|
+
queryNameProp = prop;
|
|
764
|
+
if (t.isStringLiteral(prop.value))
|
|
765
|
+
queryName = prop.value.value;
|
|
766
|
+
}
|
|
767
|
+
if (propName === 'CategoryPath')
|
|
768
|
+
hasCategoryPath = true;
|
|
769
|
+
if (propName === 'Parameters')
|
|
770
|
+
parametersNode = prop;
|
|
771
|
+
// Check invalid property names
|
|
772
|
+
if (!VALID_RUNQUERY_PROPS.has(propName)) {
|
|
773
|
+
violations.push({
|
|
774
|
+
rule: RULE_NAME,
|
|
775
|
+
severity: 'critical',
|
|
776
|
+
line: prop.loc?.start.line || 0,
|
|
777
|
+
column: prop.loc?.start.column || 0,
|
|
778
|
+
message: buildInvalidRunQueryPropertyMessage(propName),
|
|
779
|
+
code: `${propName}: ...`,
|
|
780
|
+
suggestion: DEFAULT_SUGGESTION,
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
// Validate property types
|
|
785
|
+
validateRunQueryPropertyType(propName, prop.value, prop, violations);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
// Must have QueryID or QueryName
|
|
789
|
+
if (!hasQueryID && !hasQueryName) {
|
|
790
|
+
const propsContext = foundProps.length > 0 ? ` Found properties: ${foundProps.join(', ')}.` : '';
|
|
791
|
+
const message = hasCategoryPath
|
|
792
|
+
? `RunQuery requires QueryName (or QueryID). CategoryPath alone is insufficient - it's only used to help filter when QueryName is ambiguous.${propsContext}`
|
|
793
|
+
: `RunQuery requires either QueryID or QueryName property to identify which query to run.${propsContext}`;
|
|
794
|
+
const exampleQueryName = componentSpec?.dataRequirements?.queries?.[0]?.name || 'YourQueryName';
|
|
795
|
+
violations.push({
|
|
796
|
+
rule: RULE_NAME,
|
|
797
|
+
severity: 'critical',
|
|
798
|
+
line: config.loc?.start.line || 0,
|
|
799
|
+
column: config.loc?.start.column || 0,
|
|
800
|
+
message,
|
|
801
|
+
code: `RunQuery({ QueryName: '${exampleQueryName}', ... })`,
|
|
802
|
+
suggestion: {
|
|
803
|
+
text: 'Add QueryName property to identify the query',
|
|
804
|
+
example: `await utilities.rq.RunQuery({\n QueryName: '${exampleQueryName}',${hasCategoryPath ? "\n CategoryPath: '...', // Optional, helps disambiguate" : ''}\n Parameters: { ... } // Optional query parameters\n})`,
|
|
805
|
+
},
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
// CategoryPath without QueryName — specific anti-pattern
|
|
809
|
+
if (!hasQueryID && !hasQueryName && hasCategoryPath) {
|
|
810
|
+
const exampleQueryName = componentSpec?.dataRequirements?.queries?.[0]?.name || 'YourQueryName';
|
|
811
|
+
const categoryPathProp = config.properties.find((p) => t.isObjectProperty(p) && t.isIdentifier(p.key) && p.key.name === 'CategoryPath');
|
|
812
|
+
violations.push({
|
|
813
|
+
rule: RULE_NAME,
|
|
814
|
+
severity: 'critical',
|
|
815
|
+
line: categoryPathProp?.loc?.start.line || config.loc?.start.line || 0,
|
|
816
|
+
column: categoryPathProp?.loc?.start.column || config.loc?.start.column || 0,
|
|
817
|
+
message: `CategoryPath cannot be used alone - it requires QueryName. CategoryPath is only used to disambiguate when multiple queries share the same name. You must specify which query to run using QueryName.`,
|
|
818
|
+
code: `CategoryPath: '...' // Missing: QueryName`,
|
|
819
|
+
suggestion: {
|
|
820
|
+
text: 'Add QueryName property alongside CategoryPath. The query name should come from your dataRequirements.queries[].name',
|
|
821
|
+
example: `// Query name from your spec: "${exampleQueryName}"\nawait utilities.rq.RunQuery({\n QueryName: '${exampleQueryName}', // Required: identifies which query to run\n CategoryPath: '...', // Optional: helps disambiguate if multiple queries have same name\n Parameters: {\n // Your query parameters here\n }\n})`,
|
|
822
|
+
},
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
// C. Query existence + SQL injection detection
|
|
826
|
+
const knownQueryNames = new Set(componentSpec?.dataRequirements?.queries?.map(q => q.name).filter(Boolean) ?? []);
|
|
827
|
+
if (queryName) {
|
|
828
|
+
validateQueryExistence(queryName, componentSpec, path, violations);
|
|
829
|
+
if (queryNameProp) {
|
|
830
|
+
detectSQLInjection(queryNameProp.value, path, violations, knownQueryNames);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
else if (hasQueryName && queryNameProp) {
|
|
834
|
+
// Dynamic query name — still check for SQL injection
|
|
835
|
+
detectSQLInjection(queryNameProp.value, path, violations, knownQueryNames);
|
|
836
|
+
}
|
|
837
|
+
// D. CategoryPath missing when spec requires it
|
|
838
|
+
if (queryName) {
|
|
839
|
+
validateCategoryPath(queryName, hasCategoryPath, queryNameProp, path, componentSpec, violations);
|
|
840
|
+
}
|
|
841
|
+
// E. Parameters validation
|
|
842
|
+
if (!parametersNode) {
|
|
843
|
+
// Check if missing Parameters is a problem
|
|
844
|
+
if (queryName) {
|
|
845
|
+
const specQuery = componentSpec?.dataRequirements?.queries?.find((q) => q.name === queryName);
|
|
846
|
+
validateMissingParametersProperty(queryName, specQuery, path, violations);
|
|
847
|
+
}
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
const paramValue = parametersNode.value;
|
|
851
|
+
const specQuery = queryName
|
|
852
|
+
? componentSpec?.dataRequirements?.queries?.find((q) => q.name === queryName)
|
|
853
|
+
: undefined;
|
|
854
|
+
if (t.isArrayExpression(paramValue)) {
|
|
855
|
+
validateParametersArray(paramValue, parametersNode, specQuery, violations);
|
|
856
|
+
}
|
|
857
|
+
else if (t.isObjectExpression(paramValue)) {
|
|
858
|
+
// Validate param names against spec
|
|
859
|
+
if (specQuery) {
|
|
860
|
+
validateParametersObject(paramValue, parametersNode, queryName, specQuery, violations);
|
|
861
|
+
}
|
|
862
|
+
// Validate parameter value types
|
|
863
|
+
if (queryName && componentSpec) {
|
|
864
|
+
validateParameterValueTypes(paramValue, queryName, componentSpec, violations);
|
|
865
|
+
validateIndividualParamValues(paramValue, queryName, componentSpec, stateInits, violations);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
else if (t.isIdentifier(paramValue)) {
|
|
869
|
+
if (specQuery && queryName) {
|
|
870
|
+
validateParametersVariable(paramValue, parametersNode, queryName, specQuery, typeContext, violations);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
// Other invalid type
|
|
875
|
+
validateParametersOtherType(parametersNode, specQuery, violations);
|
|
876
|
+
}
|
|
877
|
+
},
|
|
878
|
+
});
|
|
879
|
+
return violations;
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
RunQueryCallValidationRule = __decorate([
|
|
883
|
+
RegisterClass(BaseLintRule, 'runquery-call-validation')
|
|
884
|
+
], RunQueryCallValidationRule);
|
|
885
|
+
export { RunQueryCallValidationRule };
|
|
886
|
+
//# sourceMappingURL=runquery-call-validation.js.map
|