@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,1151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Inference Engine - AST-based type inference for component linting
|
|
3
|
+
*
|
|
4
|
+
* This module analyzes JavaScript AST to infer and track types throughout
|
|
5
|
+
* component code. It integrates with TypeContext to provide comprehensive
|
|
6
|
+
* type information for validation rules.
|
|
7
|
+
*/
|
|
8
|
+
import _traverse from '@babel/traverse';
|
|
9
|
+
import * as t from '@babel/types';
|
|
10
|
+
const traverse = ((_traverse.default) ?? _traverse);
|
|
11
|
+
import { TypeContext, StandardTypes, mapSQLTypeToJSType, areTypesCompatible } from './type-context.js';
|
|
12
|
+
/**
|
|
13
|
+
* TypeInferenceEngine - Analyzes AST to infer and track types
|
|
14
|
+
*/
|
|
15
|
+
export class TypeInferenceEngine {
|
|
16
|
+
constructor(componentSpec, contextUser) {
|
|
17
|
+
this.errors = [];
|
|
18
|
+
this.functionReturnTypes = new Map();
|
|
19
|
+
/** Maps setState setter names to their corresponding state variable names */
|
|
20
|
+
this.stateSetterMap = new Map();
|
|
21
|
+
this.componentSpec = componentSpec;
|
|
22
|
+
this.contextUser = contextUser;
|
|
23
|
+
this.typeContext = new TypeContext(componentSpec);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Analyze an AST and build type context
|
|
27
|
+
*/
|
|
28
|
+
async analyze(ast) {
|
|
29
|
+
this.errors = [];
|
|
30
|
+
// First pass: collect all variable declarations and their types
|
|
31
|
+
await this.collectVariableTypes(ast);
|
|
32
|
+
// Return the result
|
|
33
|
+
return {
|
|
34
|
+
typeContext: this.typeContext,
|
|
35
|
+
errors: this.errors
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get the type context after analysis
|
|
40
|
+
*/
|
|
41
|
+
getTypeContext() {
|
|
42
|
+
return this.typeContext;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get type inference errors found during analysis
|
|
46
|
+
*/
|
|
47
|
+
getErrors() {
|
|
48
|
+
return this.errors;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* First pass: collect variable types from declarations and assignments
|
|
52
|
+
*/
|
|
53
|
+
async collectVariableTypes(ast) {
|
|
54
|
+
const self = this;
|
|
55
|
+
let functionCounter = 0; // Counter for anonymous functions
|
|
56
|
+
traverse(ast, {
|
|
57
|
+
// Track variable declarations
|
|
58
|
+
VariableDeclarator(path) {
|
|
59
|
+
self.inferDeclaratorType(path);
|
|
60
|
+
// Also check if it's a function expression assignment
|
|
61
|
+
// const calculateMetrics = () => { return {...} }
|
|
62
|
+
const node = path.node;
|
|
63
|
+
if (t.isIdentifier(node.id) &&
|
|
64
|
+
(t.isArrowFunctionExpression(node.init) || t.isFunctionExpression(node.init))) {
|
|
65
|
+
self.trackFunctionReturnType(node.id.name, node.init);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
// Track assignments to existing variables
|
|
69
|
+
AssignmentExpression(path) {
|
|
70
|
+
self.inferAssignmentType(path);
|
|
71
|
+
},
|
|
72
|
+
// Track setState calls to update state variable types
|
|
73
|
+
CallExpression(path) {
|
|
74
|
+
if (t.isIdentifier(path.node.callee)) {
|
|
75
|
+
const setterName = path.node.callee.name;
|
|
76
|
+
const stateVarName = self.stateSetterMap.get(setterName);
|
|
77
|
+
if (stateVarName && path.node.arguments.length > 0) {
|
|
78
|
+
const argType = self.inferExpressionType(path.node.arguments[0], path);
|
|
79
|
+
// Only update if we inferred a meaningful type (not unknown)
|
|
80
|
+
if (argType.type !== 'unknown') {
|
|
81
|
+
self.typeContext.setVariableType(stateVarName, argType);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
// Track function parameters and return types with scoping
|
|
87
|
+
FunctionDeclaration: {
|
|
88
|
+
enter(path) {
|
|
89
|
+
const functionName = path.node.id?.name || `anon_func_${functionCounter++}`;
|
|
90
|
+
self.typeContext.enterScope(functionName);
|
|
91
|
+
self.inferFunctionParameterTypes(path);
|
|
92
|
+
// Track return type of named functions
|
|
93
|
+
if (path.node.id) {
|
|
94
|
+
self.trackFunctionReturnType(path.node.id.name, path.node);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
exit(path) {
|
|
98
|
+
self.typeContext.exitScope();
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
// Track arrow function scoping
|
|
102
|
+
ArrowFunctionExpression: {
|
|
103
|
+
enter(path) {
|
|
104
|
+
// Find the function name from parent if it's a variable declarator
|
|
105
|
+
let functionName = `anon_arrow_${functionCounter++}`;
|
|
106
|
+
const parent = path.parent;
|
|
107
|
+
if (t.isVariableDeclarator(parent) && t.isIdentifier(parent.id)) {
|
|
108
|
+
functionName = parent.id.name;
|
|
109
|
+
}
|
|
110
|
+
self.typeContext.enterScope(functionName);
|
|
111
|
+
// Try to infer callback parameter types from array method context first
|
|
112
|
+
const inferredFromCallback = self.inferCallbackParameterTypes(path);
|
|
113
|
+
if (!inferredFromCallback) {
|
|
114
|
+
self.inferArrowFunctionParameterTypes(path);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
exit(path) {
|
|
118
|
+
self.typeContext.exitScope();
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
// Track function expression scoping
|
|
122
|
+
FunctionExpression: {
|
|
123
|
+
enter(path) {
|
|
124
|
+
const functionName = path.node.id?.name || `anon_func_expr_${functionCounter++}`;
|
|
125
|
+
self.typeContext.enterScope(functionName);
|
|
126
|
+
},
|
|
127
|
+
exit(path) {
|
|
128
|
+
self.typeContext.exitScope();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Infer type from a variable declarator
|
|
135
|
+
*/
|
|
136
|
+
inferDeclaratorType(path) {
|
|
137
|
+
const node = path.node;
|
|
138
|
+
// Get variable name(s)
|
|
139
|
+
if (t.isIdentifier(node.id)) {
|
|
140
|
+
const varName = node.id.name;
|
|
141
|
+
if (node.init) {
|
|
142
|
+
const type = this.inferExpressionType(node.init, path);
|
|
143
|
+
this.typeContext.setVariableType(varName, type);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Declared but not initialized
|
|
147
|
+
this.typeContext.setVariableType(varName, { type: 'undefined', nullable: true });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else if (t.isObjectPattern(node.id) && node.init) {
|
|
151
|
+
// Destructuring: const { a, b } = obj
|
|
152
|
+
this.inferDestructuringTypes(node.id, node.init, path);
|
|
153
|
+
}
|
|
154
|
+
else if (t.isArrayPattern(node.id) && node.init) {
|
|
155
|
+
// Array destructuring: const [a, b] = arr
|
|
156
|
+
this.inferArrayDestructuringTypes(node.id, node.init, path);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Infer type from an assignment expression
|
|
161
|
+
*/
|
|
162
|
+
inferAssignmentType(path) {
|
|
163
|
+
const node = path.node;
|
|
164
|
+
if (t.isIdentifier(node.left)) {
|
|
165
|
+
const varName = node.left.name;
|
|
166
|
+
const type = this.inferExpressionType(node.right, path);
|
|
167
|
+
this.typeContext.setVariableType(varName, type);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Infer types for function parameters (component props)
|
|
172
|
+
*/
|
|
173
|
+
inferFunctionParameterTypes(path) {
|
|
174
|
+
const params = path.node.params;
|
|
175
|
+
for (const param of params) {
|
|
176
|
+
if (t.isIdentifier(param)) {
|
|
177
|
+
// Simple parameter - unknown type
|
|
178
|
+
this.typeContext.setVariableType(param.name, StandardTypes.unknown);
|
|
179
|
+
}
|
|
180
|
+
else if (t.isObjectPattern(param)) {
|
|
181
|
+
// Destructured props - this is the common component pattern
|
|
182
|
+
this.inferComponentPropsTypes(param);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Infer callback parameter types from array method context.
|
|
188
|
+
* When an arrow function is a callback to .map(), .filter(), .reduce(), etc.,
|
|
189
|
+
* the first parameter inherits the array's element type.
|
|
190
|
+
*
|
|
191
|
+
* Example: results.map(m => m.Salary)
|
|
192
|
+
* → `results` is array of entity-row(Members) → `m` is entity-row(Members)
|
|
193
|
+
*
|
|
194
|
+
* Returns true if callback parameters were inferred, false if not applicable.
|
|
195
|
+
*/
|
|
196
|
+
inferCallbackParameterTypes(path) {
|
|
197
|
+
const parent = path.parent;
|
|
198
|
+
// Check if this arrow function is the first argument to a method call
|
|
199
|
+
// Pattern: someArray.method(callback) or someArray?.method(callback)
|
|
200
|
+
if (!t.isCallExpression(parent) && !t.isOptionalCallExpression(parent))
|
|
201
|
+
return false;
|
|
202
|
+
if (parent.arguments[0] !== path.node && parent.arguments[1] !== path.node)
|
|
203
|
+
return false;
|
|
204
|
+
const callee = parent.callee;
|
|
205
|
+
if ((!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee)) || !t.isIdentifier(callee.property))
|
|
206
|
+
return false;
|
|
207
|
+
const methodName = callee.property.name;
|
|
208
|
+
const arrayIterMethods = ['map', 'filter', 'forEach', 'find', 'some', 'every', 'flatMap', 'sort'];
|
|
209
|
+
const isReduce = methodName === 'reduce';
|
|
210
|
+
if (!arrayIterMethods.includes(methodName) && !isReduce)
|
|
211
|
+
return false;
|
|
212
|
+
// Infer the type of the array being iterated
|
|
213
|
+
const arrayType = this.inferExpressionType(callee.object, path.parentPath ?? undefined);
|
|
214
|
+
if (arrayType.type !== 'array' || !arrayType.arrayElementType)
|
|
215
|
+
return false;
|
|
216
|
+
const elementType = arrayType.arrayElementType;
|
|
217
|
+
const params = path.node.params;
|
|
218
|
+
if (isReduce) {
|
|
219
|
+
// reduce((accumulator, currentValue, index, array) => ...)
|
|
220
|
+
// First param is accumulator (unknown type), second param is the element
|
|
221
|
+
if (params.length >= 1 && t.isIdentifier(params[0])) {
|
|
222
|
+
this.typeContext.setVariableType(params[0].name, StandardTypes.unknown);
|
|
223
|
+
}
|
|
224
|
+
if (params.length >= 2 && t.isIdentifier(params[1])) {
|
|
225
|
+
this.typeContext.setVariableType(params[1].name, elementType);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else if (methodName === 'sort') {
|
|
229
|
+
// sort((a, b) => ...) — both params are elements
|
|
230
|
+
if (params.length >= 1 && t.isIdentifier(params[0])) {
|
|
231
|
+
this.typeContext.setVariableType(params[0].name, elementType);
|
|
232
|
+
}
|
|
233
|
+
if (params.length >= 2 && t.isIdentifier(params[1])) {
|
|
234
|
+
this.typeContext.setVariableType(params[1].name, elementType);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
// map/filter/forEach/find/some/every/flatMap: (element, index, array) => ...
|
|
239
|
+
if (params.length >= 1 && t.isIdentifier(params[0])) {
|
|
240
|
+
this.typeContext.setVariableType(params[0].name, elementType);
|
|
241
|
+
}
|
|
242
|
+
if (params.length >= 2 && t.isIdentifier(params[1])) {
|
|
243
|
+
this.typeContext.setVariableType(params[1].name, StandardTypes.number); // index
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Handle remaining params as unknown
|
|
247
|
+
for (let i = 2; i < params.length; i++) {
|
|
248
|
+
const param = params[i];
|
|
249
|
+
if (t.isIdentifier(param)) {
|
|
250
|
+
this.typeContext.setVariableType(param.name, StandardTypes.unknown);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Infer types for arrow function parameters
|
|
257
|
+
*/
|
|
258
|
+
inferArrowFunctionParameterTypes(path) {
|
|
259
|
+
const params = path.node.params;
|
|
260
|
+
for (const param of params) {
|
|
261
|
+
if (t.isIdentifier(param)) {
|
|
262
|
+
this.typeContext.setVariableType(param.name, StandardTypes.unknown);
|
|
263
|
+
}
|
|
264
|
+
else if (t.isObjectPattern(param)) {
|
|
265
|
+
this.inferComponentPropsTypes(param);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Infer types for component props from destructuring pattern
|
|
271
|
+
*/
|
|
272
|
+
inferComponentPropsTypes(pattern) {
|
|
273
|
+
for (const prop of pattern.properties) {
|
|
274
|
+
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
|
|
275
|
+
const propName = prop.key.name;
|
|
276
|
+
// Known standard props
|
|
277
|
+
switch (propName) {
|
|
278
|
+
case 'utilities':
|
|
279
|
+
this.typeContext.setVariableType(propName, {
|
|
280
|
+
type: 'object',
|
|
281
|
+
fields: new Map([
|
|
282
|
+
['rv', { type: 'object', fromMetadata: true }], // RunView service
|
|
283
|
+
['rq', { type: 'object', fromMetadata: true }], // RunQuery service
|
|
284
|
+
['md', { type: 'object', fromMetadata: true }] // Metadata
|
|
285
|
+
])
|
|
286
|
+
});
|
|
287
|
+
break;
|
|
288
|
+
case 'styles':
|
|
289
|
+
this.typeContext.setVariableType(propName, { type: 'object' });
|
|
290
|
+
break;
|
|
291
|
+
case 'components':
|
|
292
|
+
this.typeContext.setVariableType(propName, { type: 'object' });
|
|
293
|
+
break;
|
|
294
|
+
case 'callbacks':
|
|
295
|
+
this.typeContext.setVariableType(propName, { type: 'object' });
|
|
296
|
+
break;
|
|
297
|
+
case 'savedUserSettings':
|
|
298
|
+
this.typeContext.setVariableType(propName, { type: 'object', nullable: true });
|
|
299
|
+
break;
|
|
300
|
+
case 'onSavedUserSettingsChange':
|
|
301
|
+
case 'onSaveUserSettings':
|
|
302
|
+
this.typeContext.setVariableType(propName, { type: 'function' });
|
|
303
|
+
break;
|
|
304
|
+
default:
|
|
305
|
+
// Check if it's a prop defined in the component spec
|
|
306
|
+
if (this.componentSpec?.properties) {
|
|
307
|
+
const specProp = this.componentSpec.properties.find(p => p.name === propName);
|
|
308
|
+
if (specProp) {
|
|
309
|
+
this.typeContext.setVariableType(propName, {
|
|
310
|
+
type: this.mapSpecTypeToJSType(specProp.type),
|
|
311
|
+
nullable: !specProp.required
|
|
312
|
+
});
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Check if it's an event (on* prefix)
|
|
317
|
+
if (propName.startsWith('on')) {
|
|
318
|
+
this.typeContext.setVariableType(propName, { type: 'function', nullable: true });
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
this.typeContext.setVariableType(propName, StandardTypes.unknown);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Track function return type by analyzing its body
|
|
329
|
+
*/
|
|
330
|
+
trackFunctionReturnType(functionName, func) {
|
|
331
|
+
const body = func.body;
|
|
332
|
+
// Arrow function with expression body: () => ({...})
|
|
333
|
+
if (t.isExpression(body)) {
|
|
334
|
+
const returnType = this.inferExpressionType(body);
|
|
335
|
+
this.functionReturnTypes.set(functionName, returnType);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
// Function with block body - find return statements
|
|
339
|
+
if (t.isBlockStatement(body)) {
|
|
340
|
+
// Try to analyze the function body for object building patterns
|
|
341
|
+
const returnType = this.analyzeObjectBuildingPattern(body);
|
|
342
|
+
if (returnType) {
|
|
343
|
+
this.functionReturnTypes.set(functionName, returnType);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
// Fallback: Find the first return statement
|
|
347
|
+
for (const stmt of body.body) {
|
|
348
|
+
if (t.isReturnStatement(stmt) && stmt.argument) {
|
|
349
|
+
const inferredType = this.inferExpressionType(stmt.argument);
|
|
350
|
+
this.functionReturnTypes.set(functionName, inferredType);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// No return statement found or void function
|
|
356
|
+
this.functionReturnTypes.set(functionName, StandardTypes.unknown);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Analyze common object-building patterns in function bodies
|
|
360
|
+
* Pattern: const obj = {}; forEach(...) { obj[key] = { prop: value } }; return obj;
|
|
361
|
+
*/
|
|
362
|
+
analyzeObjectBuildingPattern(body) {
|
|
363
|
+
let objectVarName = null;
|
|
364
|
+
let objectElementType = null;
|
|
365
|
+
const debugEnabled = false;
|
|
366
|
+
// Look for: const obj = {}; ... obj[key] = { ... }; ... return obj;
|
|
367
|
+
for (const stmt of body.body) {
|
|
368
|
+
// Find variable declaration: const obj = {};
|
|
369
|
+
if (t.isVariableDeclaration(stmt)) {
|
|
370
|
+
for (const decl of stmt.declarations) {
|
|
371
|
+
if (t.isIdentifier(decl.id) && t.isObjectExpression(decl.init) && decl.init.properties.length === 0) {
|
|
372
|
+
objectVarName = decl.id.name;
|
|
373
|
+
if (debugEnabled)
|
|
374
|
+
console.log('[TypeInference] Found empty object declaration:', objectVarName);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// Look for forEach or for loops that populate the object
|
|
379
|
+
if (objectVarName && t.isExpressionStatement(stmt) && t.isCallExpression(stmt.expression)) {
|
|
380
|
+
const call = stmt.expression;
|
|
381
|
+
if (t.isMemberExpression(call.callee) &&
|
|
382
|
+
t.isIdentifier(call.callee.property) &&
|
|
383
|
+
call.callee.property.name === 'forEach' &&
|
|
384
|
+
call.arguments.length > 0) {
|
|
385
|
+
const callback = call.arguments[0];
|
|
386
|
+
if ((t.isArrowFunctionExpression(callback) || t.isFunctionExpression(callback)) &&
|
|
387
|
+
t.isBlockStatement(callback.body)) {
|
|
388
|
+
// Look for obj[something] = { ... } pattern inside forEach
|
|
389
|
+
for (const innerStmt of callback.body.body) {
|
|
390
|
+
if (t.isExpressionStatement(innerStmt) &&
|
|
391
|
+
t.isAssignmentExpression(innerStmt.expression) &&
|
|
392
|
+
innerStmt.expression.operator === '=') {
|
|
393
|
+
const left = innerStmt.expression.left;
|
|
394
|
+
const right = innerStmt.expression.right;
|
|
395
|
+
// Check if left is obj[...] and right is object literal
|
|
396
|
+
if (t.isMemberExpression(left) &&
|
|
397
|
+
left.computed &&
|
|
398
|
+
t.isIdentifier(left.object) &&
|
|
399
|
+
left.object.name === objectVarName &&
|
|
400
|
+
t.isObjectExpression(right)) {
|
|
401
|
+
// Infer the type of the object literal
|
|
402
|
+
objectElementType = this.inferObjectType(right);
|
|
403
|
+
if (debugEnabled)
|
|
404
|
+
console.log('[TypeInference] Found object literal assignment, inferred type:', objectElementType);
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
// Check if this function returns the object
|
|
413
|
+
if (objectVarName && objectElementType && t.isReturnStatement(stmt)) {
|
|
414
|
+
if (t.isIdentifier(stmt.argument) && stmt.argument.name === objectVarName) {
|
|
415
|
+
// Function returns an object whose values are of the element type
|
|
416
|
+
const result = {
|
|
417
|
+
type: 'object',
|
|
418
|
+
fields: new Map(), // Dictionary, not structured object
|
|
419
|
+
objectValueType: objectElementType
|
|
420
|
+
};
|
|
421
|
+
if (debugEnabled)
|
|
422
|
+
console.log('[TypeInference] Returning object building pattern type:', result);
|
|
423
|
+
return result;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (debugEnabled && objectVarName && objectElementType) {
|
|
428
|
+
console.log('[TypeInference] Had object and element type but no return statement matched');
|
|
429
|
+
}
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Map component spec type to JavaScript type
|
|
434
|
+
*/
|
|
435
|
+
mapSpecTypeToJSType(specType) {
|
|
436
|
+
if (!specType)
|
|
437
|
+
return 'unknown';
|
|
438
|
+
const type = specType.toLowerCase();
|
|
439
|
+
if (type === 'string')
|
|
440
|
+
return 'string';
|
|
441
|
+
if (type === 'number' || type === 'int' || type === 'integer' || type === 'float' || type === 'decimal')
|
|
442
|
+
return 'number';
|
|
443
|
+
if (type === 'boolean' || type === 'bool')
|
|
444
|
+
return 'boolean';
|
|
445
|
+
if (type.startsWith('array') || type.endsWith('[]'))
|
|
446
|
+
return 'array';
|
|
447
|
+
if (type === 'object')
|
|
448
|
+
return 'object';
|
|
449
|
+
if (type === 'function')
|
|
450
|
+
return 'function';
|
|
451
|
+
return 'unknown';
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Infer types from object destructuring
|
|
455
|
+
*/
|
|
456
|
+
inferDestructuringTypes(pattern, init, path) {
|
|
457
|
+
const sourceType = this.inferExpressionType(init, path);
|
|
458
|
+
for (const prop of pattern.properties) {
|
|
459
|
+
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
|
|
460
|
+
const propName = prop.key.name;
|
|
461
|
+
const varName = t.isIdentifier(prop.value) ? prop.value.name : propName;
|
|
462
|
+
// Try to get the property type from the source
|
|
463
|
+
if (sourceType.fields?.has(propName)) {
|
|
464
|
+
const fieldType = sourceType.fields.get(propName);
|
|
465
|
+
this.typeContext.setVariableType(varName, { type: fieldType.type, nullable: fieldType.nullable });
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
this.typeContext.setVariableType(varName, StandardTypes.unknown);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
else if (t.isRestElement(prop) && t.isIdentifier(prop.argument)) {
|
|
472
|
+
// Rest element: const { a, ...rest } = obj
|
|
473
|
+
this.typeContext.setVariableType(prop.argument.name, { type: 'object' });
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Infer types from array destructuring
|
|
479
|
+
*/
|
|
480
|
+
inferArrayDestructuringTypes(pattern, init, path) {
|
|
481
|
+
const sourceType = this.inferExpressionType(init, path);
|
|
482
|
+
// Detect useState pattern: const [state, setState] = useState(initialValue)
|
|
483
|
+
const isUseState = t.isCallExpression(init) &&
|
|
484
|
+
t.isIdentifier(init.callee) && init.callee.name === 'useState';
|
|
485
|
+
for (let i = 0; i < pattern.elements.length; i++) {
|
|
486
|
+
const element = pattern.elements[i];
|
|
487
|
+
if (t.isIdentifier(element)) {
|
|
488
|
+
if (isUseState && i === 1) {
|
|
489
|
+
// Second element is the setter function — register the state/setter pair
|
|
490
|
+
this.typeContext.setVariableType(element.name, StandardTypes.function);
|
|
491
|
+
const stateVar = pattern.elements[0];
|
|
492
|
+
if (t.isIdentifier(stateVar)) {
|
|
493
|
+
this.stateSetterMap.set(element.name, stateVar.name);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
else if (sourceType.arrayElementType) {
|
|
497
|
+
this.typeContext.setVariableType(element.name, sourceType.arrayElementType);
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
this.typeContext.setVariableType(element.name, StandardTypes.unknown);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
else if (t.isRestElement(element) && t.isIdentifier(element.argument)) {
|
|
504
|
+
// Rest element: const [a, ...rest] = arr
|
|
505
|
+
this.typeContext.setVariableType(element.argument.name, {
|
|
506
|
+
type: 'array',
|
|
507
|
+
arrayElementType: sourceType.arrayElementType
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Infer the type of an expression
|
|
514
|
+
*/
|
|
515
|
+
inferExpressionType(node, path) {
|
|
516
|
+
// Literals - now track actual values for constant analysis
|
|
517
|
+
if (t.isStringLiteral(node)) {
|
|
518
|
+
return { ...StandardTypes.string, literalValue: node.value };
|
|
519
|
+
}
|
|
520
|
+
if (t.isTemplateLiteral(node)) {
|
|
521
|
+
// For template literals, we can't always know the final value
|
|
522
|
+
return StandardTypes.string;
|
|
523
|
+
}
|
|
524
|
+
if (t.isNumericLiteral(node)) {
|
|
525
|
+
return { ...StandardTypes.number, literalValue: node.value };
|
|
526
|
+
}
|
|
527
|
+
if (t.isBooleanLiteral(node)) {
|
|
528
|
+
return { ...StandardTypes.boolean, literalValue: node.value };
|
|
529
|
+
}
|
|
530
|
+
if (t.isNullLiteral(node)) {
|
|
531
|
+
return { ...StandardTypes.null, literalValue: null };
|
|
532
|
+
}
|
|
533
|
+
if (t.isArrayExpression(node)) {
|
|
534
|
+
return this.inferArrayType(node, path);
|
|
535
|
+
}
|
|
536
|
+
if (t.isObjectExpression(node)) {
|
|
537
|
+
return this.inferObjectType(node, path);
|
|
538
|
+
}
|
|
539
|
+
// Identifiers (variable references)
|
|
540
|
+
if (t.isIdentifier(node)) {
|
|
541
|
+
return this.typeContext.getVariableType(node.name) || StandardTypes.unknown;
|
|
542
|
+
}
|
|
543
|
+
// Function expressions
|
|
544
|
+
if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) {
|
|
545
|
+
return StandardTypes.function;
|
|
546
|
+
}
|
|
547
|
+
// Call expressions (including optional: a?.b())
|
|
548
|
+
if (t.isCallExpression(node) || t.isOptionalCallExpression(node)) {
|
|
549
|
+
return this.inferCallExpressionType(node, path);
|
|
550
|
+
}
|
|
551
|
+
// New expressions (constructor calls like new Date())
|
|
552
|
+
if (t.isNewExpression(node)) {
|
|
553
|
+
return this.inferNewExpressionType(node, path);
|
|
554
|
+
}
|
|
555
|
+
// Member expressions (including optional chaining: a?.b, a?.[0])
|
|
556
|
+
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
|
|
557
|
+
return this.inferMemberExpressionType(node, path);
|
|
558
|
+
}
|
|
559
|
+
// Binary expressions
|
|
560
|
+
if (t.isBinaryExpression(node)) {
|
|
561
|
+
return this.inferBinaryExpressionType(node, path);
|
|
562
|
+
}
|
|
563
|
+
// Unary expressions
|
|
564
|
+
if (t.isUnaryExpression(node)) {
|
|
565
|
+
return this.inferUnaryExpressionType(node);
|
|
566
|
+
}
|
|
567
|
+
// Conditional (ternary) expressions
|
|
568
|
+
if (t.isConditionalExpression(node)) {
|
|
569
|
+
// Return the type of the consequent (or alternate if different, return unknown)
|
|
570
|
+
const consequentType = this.inferExpressionType(node.consequent, path);
|
|
571
|
+
const alternateType = this.inferExpressionType(node.alternate, path);
|
|
572
|
+
if (areTypesCompatible(consequentType, alternateType)) {
|
|
573
|
+
return consequentType;
|
|
574
|
+
}
|
|
575
|
+
return StandardTypes.unknown;
|
|
576
|
+
}
|
|
577
|
+
// Logical expressions (&&, ||, ??)
|
|
578
|
+
if (t.isLogicalExpression(node)) {
|
|
579
|
+
// For ||, the result is the first truthy value
|
|
580
|
+
// For &&, the result is the first falsy or last value
|
|
581
|
+
// For ??, the result is the first non-nullish value
|
|
582
|
+
return this.inferExpressionType(node.right, path);
|
|
583
|
+
}
|
|
584
|
+
// Await expressions
|
|
585
|
+
if (t.isAwaitExpression(node)) {
|
|
586
|
+
return this.inferExpressionType(node.argument, path);
|
|
587
|
+
}
|
|
588
|
+
return StandardTypes.unknown;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Infer type for array expressions
|
|
592
|
+
*/
|
|
593
|
+
inferArrayType(node, path) {
|
|
594
|
+
if (node.elements.length === 0) {
|
|
595
|
+
return { type: 'array', arrayElementType: StandardTypes.unknown };
|
|
596
|
+
}
|
|
597
|
+
// Try to infer element type from first element
|
|
598
|
+
const firstElement = node.elements[0];
|
|
599
|
+
if (firstElement && !t.isSpreadElement(firstElement)) {
|
|
600
|
+
const elementType = this.inferExpressionType(firstElement, path);
|
|
601
|
+
return { type: 'array', arrayElementType: elementType };
|
|
602
|
+
}
|
|
603
|
+
return { type: 'array', arrayElementType: StandardTypes.unknown };
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Infer type for object expressions
|
|
607
|
+
*/
|
|
608
|
+
inferObjectType(node, path) {
|
|
609
|
+
const fields = new Map();
|
|
610
|
+
for (const prop of node.properties) {
|
|
611
|
+
// Handle spread elements: { ...otherObject, newProp: value }
|
|
612
|
+
if (t.isSpreadElement(prop)) {
|
|
613
|
+
const spreadType = this.inferExpressionType(prop.argument, path);
|
|
614
|
+
// If spreading an object, merge its fields into our fields map
|
|
615
|
+
if (spreadType.type === 'object' && spreadType.fields) {
|
|
616
|
+
for (const [fieldName, fieldInfo] of spreadType.fields.entries()) {
|
|
617
|
+
// Only add if not already present (later properties override spread)
|
|
618
|
+
if (!fields.has(fieldName)) {
|
|
619
|
+
fields.set(fieldName, fieldInfo);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
// Handle regular object properties
|
|
625
|
+
else if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
|
|
626
|
+
const propName = prop.key.name;
|
|
627
|
+
const propType = this.inferExpressionType(prop.value, path);
|
|
628
|
+
fields.set(propName, {
|
|
629
|
+
type: propType.type,
|
|
630
|
+
fromMetadata: false,
|
|
631
|
+
nullable: propType.nullable
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return { type: 'object', fields };
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Infer type for call expressions
|
|
639
|
+
*/
|
|
640
|
+
inferCallExpressionType(node, path) {
|
|
641
|
+
// Check for user-defined function calls
|
|
642
|
+
if (t.isIdentifier(node.callee)) {
|
|
643
|
+
const functionName = node.callee.name;
|
|
644
|
+
// Check if we've tracked this function's return type
|
|
645
|
+
const returnType = this.functionReturnTypes.get(functionName);
|
|
646
|
+
if (returnType) {
|
|
647
|
+
return returnType;
|
|
648
|
+
}
|
|
649
|
+
// Check for React hooks that return computed values
|
|
650
|
+
// useMemo(() => { return {...} }, [deps]) - returns the memoized value
|
|
651
|
+
if (functionName === 'useMemo' && node.arguments.length > 0) {
|
|
652
|
+
const callback = node.arguments[0];
|
|
653
|
+
// Extract the return type from the callback
|
|
654
|
+
if (t.isArrowFunctionExpression(callback) || t.isFunctionExpression(callback)) {
|
|
655
|
+
const body = callback.body;
|
|
656
|
+
// Arrow function with expression body: useMemo(() => ({...}))
|
|
657
|
+
if (t.isExpression(body)) {
|
|
658
|
+
return this.inferExpressionType(body, path);
|
|
659
|
+
}
|
|
660
|
+
// Arrow function with block body: useMemo(() => { return {...} })
|
|
661
|
+
if (t.isBlockStatement(body)) {
|
|
662
|
+
// Find return statement
|
|
663
|
+
for (const stmt of body.body) {
|
|
664
|
+
if (t.isReturnStatement(stmt) && stmt.argument) {
|
|
665
|
+
return this.inferExpressionType(stmt.argument, path);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
// useCallback returns a function
|
|
672
|
+
if (functionName === 'useCallback') {
|
|
673
|
+
return StandardTypes.function;
|
|
674
|
+
}
|
|
675
|
+
// useState(initialValue) — return the type of the initial value so that
|
|
676
|
+
// array destructuring `const [state, setState] = useState(X)` propagates X's type to `state`
|
|
677
|
+
if (functionName === 'useState') {
|
|
678
|
+
if (node.arguments.length > 0) {
|
|
679
|
+
const initType = this.inferExpressionType(node.arguments[0], path);
|
|
680
|
+
// For null/undefined initializers, use unknown+nullable to avoid false positives
|
|
681
|
+
// (the actual type will be set by setState calls later)
|
|
682
|
+
if (initType.type === 'null' || initType.type === 'undefined') {
|
|
683
|
+
return { type: 'array', arrayElementType: { ...StandardTypes.unknown, nullable: true } };
|
|
684
|
+
}
|
|
685
|
+
return { type: 'array', arrayElementType: initType };
|
|
686
|
+
}
|
|
687
|
+
// useState() with no argument — state is undefined/nullable
|
|
688
|
+
return { type: 'array', arrayElementType: { ...StandardTypes.unknown, nullable: true } };
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
// Check for RunView/RunQuery calls
|
|
692
|
+
if (t.isMemberExpression(node.callee) || t.isOptionalMemberExpression(node.callee)) {
|
|
693
|
+
const calleeObj = node.callee.object;
|
|
694
|
+
const calleeProp = node.callee.property;
|
|
695
|
+
// utilities.rv.RunView or utilities.rv.RunViews
|
|
696
|
+
if ((t.isMemberExpression(calleeObj) || t.isOptionalMemberExpression(calleeObj)) &&
|
|
697
|
+
t.isIdentifier(calleeObj.property) &&
|
|
698
|
+
t.isIdentifier(calleeProp)) {
|
|
699
|
+
const serviceName = calleeObj.property.name;
|
|
700
|
+
const methodName = calleeProp.name;
|
|
701
|
+
if (serviceName === 'rv' && (methodName === 'RunView' || methodName === 'RunViews')) {
|
|
702
|
+
return this.inferRunViewResultType(node);
|
|
703
|
+
}
|
|
704
|
+
if (serviceName === 'rq' && methodName === 'RunQuery') {
|
|
705
|
+
return this.inferRunQueryResultType(node);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
// Object.values() - returns array of object's values
|
|
710
|
+
if ((t.isMemberExpression(node.callee) || t.isOptionalMemberExpression(node.callee)) &&
|
|
711
|
+
t.isIdentifier(node.callee.object) &&
|
|
712
|
+
node.callee.object.name === 'Object' &&
|
|
713
|
+
t.isIdentifier(node.callee.property) &&
|
|
714
|
+
node.callee.property.name === 'values' &&
|
|
715
|
+
node.arguments.length === 1) {
|
|
716
|
+
const objectType = this.inferExpressionType(node.arguments[0], path);
|
|
717
|
+
if (objectType.type === 'object' && objectType.objectValueType) {
|
|
718
|
+
// Return array of the object's value type
|
|
719
|
+
return {
|
|
720
|
+
type: 'array',
|
|
721
|
+
arrayElementType: objectType.objectValueType
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
// Fallback: unknown array
|
|
725
|
+
return { type: 'array', arrayElementType: StandardTypes.unknown };
|
|
726
|
+
}
|
|
727
|
+
// Array methods that return arrays
|
|
728
|
+
if ((t.isMemberExpression(node.callee) || t.isOptionalMemberExpression(node.callee)) && t.isIdentifier(node.callee.property)) {
|
|
729
|
+
const methodName = node.callee.property.name;
|
|
730
|
+
const arrayMethods = ['filter', 'map', 'slice', 'concat', 'flat', 'flatMap', 'sort', 'reverse'];
|
|
731
|
+
if (arrayMethods.includes(methodName)) {
|
|
732
|
+
const arrayType = this.inferExpressionType(node.callee.object, path);
|
|
733
|
+
if (arrayType.type === 'array') {
|
|
734
|
+
// For map, the element type might change
|
|
735
|
+
if (methodName === 'map') {
|
|
736
|
+
return { type: 'array', arrayElementType: StandardTypes.unknown };
|
|
737
|
+
}
|
|
738
|
+
return arrayType;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
// Array methods that return elements
|
|
742
|
+
if (methodName === 'find') {
|
|
743
|
+
const arrayType = this.inferExpressionType(node.callee.object, path);
|
|
744
|
+
if (arrayType.arrayElementType) {
|
|
745
|
+
return { ...arrayType.arrayElementType, nullable: true };
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
// Methods that return numbers
|
|
749
|
+
if (['indexOf', 'findIndex', 'length'].includes(methodName)) {
|
|
750
|
+
return StandardTypes.number;
|
|
751
|
+
}
|
|
752
|
+
// Methods that return booleans
|
|
753
|
+
if (['includes', 'some', 'every'].includes(methodName)) {
|
|
754
|
+
return StandardTypes.boolean;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return StandardTypes.unknown;
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Infer type for new expressions (constructor calls)
|
|
761
|
+
*/
|
|
762
|
+
inferNewExpressionType(node, path) {
|
|
763
|
+
// Check for common constructors
|
|
764
|
+
if (t.isIdentifier(node.callee)) {
|
|
765
|
+
const constructorName = node.callee.name;
|
|
766
|
+
// Date constructor
|
|
767
|
+
if (constructorName === 'Date') {
|
|
768
|
+
return { type: 'Date', fromMetadata: false };
|
|
769
|
+
}
|
|
770
|
+
// Array constructor
|
|
771
|
+
if (constructorName === 'Array') {
|
|
772
|
+
return { type: 'array', arrayElementType: StandardTypes.unknown };
|
|
773
|
+
}
|
|
774
|
+
// Object constructor
|
|
775
|
+
if (constructorName === 'Object') {
|
|
776
|
+
return { type: 'object', fields: new Map() };
|
|
777
|
+
}
|
|
778
|
+
// Map constructor
|
|
779
|
+
if (constructorName === 'Map') {
|
|
780
|
+
return { type: 'Map', fromMetadata: false };
|
|
781
|
+
}
|
|
782
|
+
// Set constructor
|
|
783
|
+
if (constructorName === 'Set') {
|
|
784
|
+
return { type: 'Set', fromMetadata: false };
|
|
785
|
+
}
|
|
786
|
+
// RegExp constructor
|
|
787
|
+
if (constructorName === 'RegExp') {
|
|
788
|
+
return { type: 'RegExp', fromMetadata: false };
|
|
789
|
+
}
|
|
790
|
+
// Error and related constructors
|
|
791
|
+
if (['Error', 'TypeError', 'RangeError', 'ReferenceError', 'SyntaxError'].includes(constructorName)) {
|
|
792
|
+
return { type: 'Error', fromMetadata: false };
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return StandardTypes.unknown;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Infer type for RunView result
|
|
799
|
+
*/
|
|
800
|
+
inferRunViewResultType(node) {
|
|
801
|
+
// Try to extract EntityName from the arguments
|
|
802
|
+
let entityName;
|
|
803
|
+
if (node.arguments.length > 0 && t.isObjectExpression(node.arguments[0])) {
|
|
804
|
+
for (const prop of node.arguments[0].properties) {
|
|
805
|
+
if (t.isObjectProperty(prop) &&
|
|
806
|
+
t.isIdentifier(prop.key) &&
|
|
807
|
+
prop.key.name === 'EntityName' &&
|
|
808
|
+
t.isStringLiteral(prop.value)) {
|
|
809
|
+
entityName = prop.value.value;
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return this.typeContext.createRunViewResultType(entityName || 'unknown');
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Infer type for RunQuery result
|
|
818
|
+
*/
|
|
819
|
+
inferRunQueryResultType(node) {
|
|
820
|
+
// Try to extract QueryName and Parameters from the arguments
|
|
821
|
+
let queryName;
|
|
822
|
+
let parametersNode;
|
|
823
|
+
if (node.arguments.length > 0 && t.isObjectExpression(node.arguments[0])) {
|
|
824
|
+
for (const prop of node.arguments[0].properties) {
|
|
825
|
+
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
|
|
826
|
+
if (prop.key.name === 'QueryName' && t.isStringLiteral(prop.value)) {
|
|
827
|
+
queryName = prop.value.value;
|
|
828
|
+
}
|
|
829
|
+
else if (prop.key.name === 'Parameters' && t.isObjectExpression(prop.value)) {
|
|
830
|
+
parametersNode = prop.value;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
// Validate query parameters if we have both queryName and parameters
|
|
836
|
+
if (queryName && parametersNode) {
|
|
837
|
+
this.validateQueryParameters(queryName, parametersNode);
|
|
838
|
+
}
|
|
839
|
+
return this.typeContext.createRunQueryResultType(queryName || 'unknown');
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Validate query parameters, especially date parameters
|
|
843
|
+
*/
|
|
844
|
+
validateQueryParameters(queryName, parametersNode) {
|
|
845
|
+
// Get the query definition from component spec
|
|
846
|
+
const query = this.componentSpec?.dataRequirements?.queries?.find(q => q.name === queryName);
|
|
847
|
+
if (!query?.parameters) {
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
// Build a map of parameter types with isRequired flag
|
|
851
|
+
const paramTypeMap = new Map();
|
|
852
|
+
for (const param of query.parameters) {
|
|
853
|
+
const extParam = param;
|
|
854
|
+
if (extParam.type) {
|
|
855
|
+
paramTypeMap.set(param.name.toLowerCase(), {
|
|
856
|
+
type: mapSQLTypeToJSType(extParam.type),
|
|
857
|
+
sqlType: extParam.type,
|
|
858
|
+
isRequired: extParam.isRequired === true
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
// Validate each parameter value
|
|
863
|
+
for (const prop of parametersNode.properties) {
|
|
864
|
+
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
|
|
865
|
+
const paramName = prop.key.name;
|
|
866
|
+
const paramTypeInfo = paramTypeMap.get(paramName.toLowerCase());
|
|
867
|
+
if (!paramTypeInfo) {
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
// Check for date/datetime types
|
|
871
|
+
const isDateType = ['date', 'datetime', 'datetime2', 'smalldatetime', 'datetimeoffset']
|
|
872
|
+
.includes(paramTypeInfo.sqlType.toLowerCase());
|
|
873
|
+
if (isDateType) {
|
|
874
|
+
// Handle string literals: StartDate: '2024-01-01'
|
|
875
|
+
if (t.isStringLiteral(prop.value)) {
|
|
876
|
+
this.validateDateParameter(paramName, prop.value.value, paramTypeInfo.isRequired, prop.loc);
|
|
877
|
+
}
|
|
878
|
+
// Handle variables: StartDate: effectiveStartDate
|
|
879
|
+
else if (t.isIdentifier(prop.value)) {
|
|
880
|
+
this.validateDateVariable(paramName, prop.value.name, paramTypeInfo.isRequired, prop.loc);
|
|
881
|
+
}
|
|
882
|
+
// Handle logical expressions: StartDate: startDate || appliedStartDate
|
|
883
|
+
else if (t.isLogicalExpression(prop.value)) {
|
|
884
|
+
// For logical expressions, check each operand
|
|
885
|
+
this.validateDateLogicalExpression(paramName, prop.value, paramTypeInfo.isRequired, prop.loc);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Validate a date variable used as a parameter
|
|
893
|
+
*/
|
|
894
|
+
validateDateVariable(paramName, variableName, isRequired, loc) {
|
|
895
|
+
// Look up the variable type in the type context
|
|
896
|
+
const varType = this.typeContext.getVariableType(variableName);
|
|
897
|
+
if (!varType) {
|
|
898
|
+
// Variable type unknown - could be a function parameter with no type info
|
|
899
|
+
// We'll allow this for now (no warning)
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
// Handle 'unknown' type - common with React useState(null)
|
|
903
|
+
if (varType.type === 'unknown') {
|
|
904
|
+
// For optional parameters, unknown is acceptable (likely React state)
|
|
905
|
+
// For required parameters, emit a warning suggesting validation
|
|
906
|
+
if (isRequired) {
|
|
907
|
+
this.errors.push({
|
|
908
|
+
type: 'warning',
|
|
909
|
+
message: `Parameter "${paramName}" is required but variable "${variableName}" has unknown type. If "${variableName}" comes from React state (useState), ensure it's validated before calling RunQuery. Add a guard: if (!${variableName}) { return; }`,
|
|
910
|
+
line: loc?.start.line || 0,
|
|
911
|
+
column: loc?.start.column || 0,
|
|
912
|
+
code: `// Add validation before RunQuery:\nif (!${variableName}) {\n return; // or show error to user\n}`
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
// For optional parameters with unknown type, allow silently
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
// Check if the variable is typed as string (dates are passed as ISO strings)
|
|
919
|
+
if (varType.type !== 'string') {
|
|
920
|
+
this.errors.push({
|
|
921
|
+
type: 'error',
|
|
922
|
+
message: `Parameter "${paramName}" expects a date string, but variable "${variableName}" has type "${varType.type}". Date parameters must be ISO date strings (e.g., '2024-01-01').`,
|
|
923
|
+
line: loc?.start.line || 0,
|
|
924
|
+
column: loc?.start.column || 0,
|
|
925
|
+
code: `// Ensure ${variableName} contains a valid ISO date string`
|
|
926
|
+
});
|
|
927
|
+
return; // Stop further validation if type is wrong
|
|
928
|
+
}
|
|
929
|
+
// If we know the literal value (constant), validate it as a date string
|
|
930
|
+
if (varType.literalValue !== undefined && typeof varType.literalValue === 'string') {
|
|
931
|
+
this.validateDateParameter(paramName, varType.literalValue, isRequired, loc);
|
|
932
|
+
return; // Already validated the literal value
|
|
933
|
+
}
|
|
934
|
+
// If the variable is nullable and the parameter is required, flag it
|
|
935
|
+
if (isRequired && varType.nullable) {
|
|
936
|
+
this.errors.push({
|
|
937
|
+
type: 'error',
|
|
938
|
+
message: `Parameter "${paramName}" is required but variable "${variableName}" may be null/undefined. Ensure "${variableName}" always has a valid date value before calling RunQuery.`,
|
|
939
|
+
line: loc?.start.line || 0,
|
|
940
|
+
column: loc?.start.column || 0,
|
|
941
|
+
code: `// Add validation:\nif (!${variableName}) {\n throw new Error('${paramName} is required');\n}`
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Validate a logical expression (e.g., startDate || appliedStartDate)
|
|
947
|
+
*/
|
|
948
|
+
validateDateLogicalExpression(paramName, node, isRequired, loc) {
|
|
949
|
+
// For || (OR) expressions, validate that at least one operand provides a valid date
|
|
950
|
+
// For && (AND) expressions, validate the right operand (result of the expression)
|
|
951
|
+
// For ?? (nullish coalescing), validate the right operand (fallback value)
|
|
952
|
+
const operator = node.operator;
|
|
953
|
+
if (operator === '||' || operator === '??') {
|
|
954
|
+
// Validate both sides - if left is null/undefined, right will be used
|
|
955
|
+
if (t.isIdentifier(node.left)) {
|
|
956
|
+
this.validateDateVariable(paramName, node.left.name, false, loc); // Left can be nullable
|
|
957
|
+
}
|
|
958
|
+
else if (t.isStringLiteral(node.left)) {
|
|
959
|
+
this.validateDateParameter(paramName, node.left.value, false, loc);
|
|
960
|
+
}
|
|
961
|
+
if (t.isIdentifier(node.right)) {
|
|
962
|
+
this.validateDateVariable(paramName, node.right.name, isRequired, loc); // Right inherits requirement
|
|
963
|
+
}
|
|
964
|
+
else if (t.isStringLiteral(node.right)) {
|
|
965
|
+
this.validateDateParameter(paramName, node.right.value, isRequired, loc);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
else if (operator === '&&') {
|
|
969
|
+
// For AND, only the right side matters as the result
|
|
970
|
+
if (t.isIdentifier(node.right)) {
|
|
971
|
+
this.validateDateVariable(paramName, node.right.name, isRequired, loc);
|
|
972
|
+
}
|
|
973
|
+
else if (t.isStringLiteral(node.right)) {
|
|
974
|
+
this.validateDateParameter(paramName, node.right.value, isRequired, loc);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Validate a date parameter value
|
|
980
|
+
*/
|
|
981
|
+
validateDateParameter(paramName, value, isRequired, loc) {
|
|
982
|
+
// Empty strings are invalid for date parameters
|
|
983
|
+
if (value === '') {
|
|
984
|
+
let message;
|
|
985
|
+
let code;
|
|
986
|
+
if (isRequired) {
|
|
987
|
+
message = `Parameter "${paramName}" is required and must have a valid ISO date value (e.g., '2024-01-01'). Empty strings are not allowed.`;
|
|
988
|
+
code = `${paramName}: '2024-01-01' // Required parameter`;
|
|
989
|
+
}
|
|
990
|
+
else {
|
|
991
|
+
message = `Parameter "${paramName}" is an optional date parameter but has an empty string value. Either provide a valid ISO date (e.g., '2024-01-01') or omit the parameter entirely from the Parameters object.`;
|
|
992
|
+
code = `// Option 1: Provide valid date\n${paramName}: '2024-01-01'\n\n// Option 2: Remove parameter entirely\nParameters: {\n // ${paramName}: <omitted>\n}`;
|
|
993
|
+
}
|
|
994
|
+
this.errors.push({
|
|
995
|
+
type: 'error',
|
|
996
|
+
message,
|
|
997
|
+
line: loc?.start.line || 0,
|
|
998
|
+
column: loc?.start.column || 0,
|
|
999
|
+
code
|
|
1000
|
+
});
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
// Validate ISO date format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS
|
|
1004
|
+
const isoDatePattern = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|[+-]\d{2}:\d{2})?)?$/;
|
|
1005
|
+
if (!isoDatePattern.test(value)) {
|
|
1006
|
+
this.errors.push({
|
|
1007
|
+
type: 'error',
|
|
1008
|
+
message: `Parameter "${paramName}" is a date parameter but value '${value}' is not a valid ISO date format. Use YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS format.`,
|
|
1009
|
+
line: loc?.start.line || 0,
|
|
1010
|
+
column: loc?.start.column || 0,
|
|
1011
|
+
code: `${paramName}: '2024-01-01' // or '2024-01-01T00:00:00'`
|
|
1012
|
+
});
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
// Validate that it's an actual valid date (not 2024-13-45)
|
|
1016
|
+
const parsedDate = new Date(value);
|
|
1017
|
+
if (isNaN(parsedDate.getTime())) {
|
|
1018
|
+
this.errors.push({
|
|
1019
|
+
type: 'error',
|
|
1020
|
+
message: `Parameter "${paramName}" has an invalid date value '${value}'. The date format is correct but the date itself is invalid (e.g., month > 12, day > 31).`,
|
|
1021
|
+
line: loc?.start.line || 0,
|
|
1022
|
+
column: loc?.start.column || 0,
|
|
1023
|
+
code: `${paramName}: '2024-01-01' // Use a valid date`
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Infer type for member expressions
|
|
1029
|
+
*/
|
|
1030
|
+
inferMemberExpressionType(node, path) {
|
|
1031
|
+
const objectType = this.inferExpressionType(node.object, path);
|
|
1032
|
+
// Array index access
|
|
1033
|
+
if (node.computed && t.isNumericLiteral(node.property)) {
|
|
1034
|
+
if (objectType.arrayElementType) {
|
|
1035
|
+
return objectType.arrayElementType;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
// Property access
|
|
1039
|
+
if (t.isIdentifier(node.property)) {
|
|
1040
|
+
const propName = node.property.name;
|
|
1041
|
+
// Special case: .Results property on RunView/RunQuery result
|
|
1042
|
+
// Must be checked BEFORE generic field lookup, because FieldTypeInfo
|
|
1043
|
+
// can't carry arrayElementType (it's a simplified type), so the generic
|
|
1044
|
+
// lookup would return bare { type: 'array' } without entity-row info.
|
|
1045
|
+
if (propName === 'Results' && objectType.type === 'object') {
|
|
1046
|
+
if (objectType.entityName) {
|
|
1047
|
+
const entityFields = this.typeContext.getEntityFieldTypesSync(objectType.entityName);
|
|
1048
|
+
return {
|
|
1049
|
+
type: 'array',
|
|
1050
|
+
arrayElementType: {
|
|
1051
|
+
type: 'entity-row',
|
|
1052
|
+
entityName: objectType.entityName,
|
|
1053
|
+
fields: entityFields.size > 0 ? entityFields : undefined
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
if (objectType.queryName) {
|
|
1058
|
+
const queryFields = this.typeContext.getQueryFieldTypes(objectType.queryName);
|
|
1059
|
+
return {
|
|
1060
|
+
type: 'array',
|
|
1061
|
+
arrayElementType: {
|
|
1062
|
+
type: 'query-row',
|
|
1063
|
+
queryName: objectType.queryName,
|
|
1064
|
+
fields: queryFields ?? undefined
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
// Check if the object has known fields
|
|
1070
|
+
if (objectType.fields?.has(propName)) {
|
|
1071
|
+
const field = objectType.fields.get(propName);
|
|
1072
|
+
return { type: field.type, nullable: field.nullable };
|
|
1073
|
+
}
|
|
1074
|
+
// Array length
|
|
1075
|
+
if (propName === 'length' && objectType.type === 'array') {
|
|
1076
|
+
return StandardTypes.number;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return StandardTypes.unknown;
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Infer type for binary expressions
|
|
1083
|
+
*/
|
|
1084
|
+
inferBinaryExpressionType(node, path) {
|
|
1085
|
+
const operator = node.operator;
|
|
1086
|
+
// Comparison operators always return boolean
|
|
1087
|
+
if (['==', '!=', '===', '!==', '<', '<=', '>', '>=', 'in', 'instanceof'].includes(operator)) {
|
|
1088
|
+
return StandardTypes.boolean;
|
|
1089
|
+
}
|
|
1090
|
+
// Arithmetic operators return number
|
|
1091
|
+
if (['-', '*', '/', '%', '**', '|', '&', '^', '<<', '>>', '>>>'].includes(operator)) {
|
|
1092
|
+
return StandardTypes.number;
|
|
1093
|
+
}
|
|
1094
|
+
// + can be string or number
|
|
1095
|
+
if (operator === '+') {
|
|
1096
|
+
// node.left can be PrivateName in some cases, skip type inference for those
|
|
1097
|
+
if (t.isPrivateName(node.left)) {
|
|
1098
|
+
return StandardTypes.unknown;
|
|
1099
|
+
}
|
|
1100
|
+
const leftType = this.inferExpressionType(node.left, path);
|
|
1101
|
+
const rightType = this.inferExpressionType(node.right, path);
|
|
1102
|
+
if (leftType.type === 'string' || rightType.type === 'string') {
|
|
1103
|
+
return StandardTypes.string;
|
|
1104
|
+
}
|
|
1105
|
+
if (leftType.type === 'number' && rightType.type === 'number') {
|
|
1106
|
+
return StandardTypes.number;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return StandardTypes.unknown;
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Infer type for unary expressions
|
|
1113
|
+
*/
|
|
1114
|
+
inferUnaryExpressionType(node) {
|
|
1115
|
+
const operator = node.operator;
|
|
1116
|
+
if (operator === '!') {
|
|
1117
|
+
return StandardTypes.boolean;
|
|
1118
|
+
}
|
|
1119
|
+
if (operator === 'typeof') {
|
|
1120
|
+
return StandardTypes.string;
|
|
1121
|
+
}
|
|
1122
|
+
if (['+', '-', '~'].includes(operator)) {
|
|
1123
|
+
return StandardTypes.number;
|
|
1124
|
+
}
|
|
1125
|
+
if (operator === 'void') {
|
|
1126
|
+
return StandardTypes.undefined;
|
|
1127
|
+
}
|
|
1128
|
+
return StandardTypes.unknown;
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Get inferred type for a variable by name
|
|
1132
|
+
*/
|
|
1133
|
+
getVariableType(name) {
|
|
1134
|
+
return this.typeContext.getVariableType(name);
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Check if an expression is a specific type
|
|
1138
|
+
*/
|
|
1139
|
+
isType(node, expectedType, path) {
|
|
1140
|
+
const actualType = this.inferExpressionType(node, path);
|
|
1141
|
+
return actualType.type === expectedType;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Convenience function to analyze an AST and return type context
|
|
1146
|
+
*/
|
|
1147
|
+
export async function analyzeTypes(ast, componentSpec, contextUser) {
|
|
1148
|
+
const engine = new TypeInferenceEngine(componentSpec, contextUser);
|
|
1149
|
+
return engine.analyze(ast);
|
|
1150
|
+
}
|
|
1151
|
+
//# sourceMappingURL=type-inference-engine.js.map
|