driftdetect-detectors 0.1.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/accessibility/alt-text.d.ts +63 -0
- package/dist/accessibility/alt-text.d.ts.map +1 -0
- package/dist/accessibility/alt-text.js +100 -0
- package/dist/accessibility/alt-text.js.map +1 -0
- package/dist/accessibility/aria-roles.d.ts +65 -0
- package/dist/accessibility/aria-roles.d.ts.map +1 -0
- package/dist/accessibility/aria-roles.js +87 -0
- package/dist/accessibility/aria-roles.js.map +1 -0
- package/dist/accessibility/focus-management.d.ts +62 -0
- package/dist/accessibility/focus-management.d.ts.map +1 -0
- package/dist/accessibility/focus-management.js +88 -0
- package/dist/accessibility/focus-management.js.map +1 -0
- package/dist/accessibility/heading-hierarchy.d.ts +66 -0
- package/dist/accessibility/heading-hierarchy.d.ts.map +1 -0
- package/dist/accessibility/heading-hierarchy.js +94 -0
- package/dist/accessibility/heading-hierarchy.js.map +1 -0
- package/dist/accessibility/index.d.ts +25 -0
- package/dist/accessibility/index.d.ts.map +1 -0
- package/dist/accessibility/index.js +21 -0
- package/dist/accessibility/index.js.map +1 -0
- package/dist/accessibility/keyboard-nav.d.ts +63 -0
- package/dist/accessibility/keyboard-nav.d.ts.map +1 -0
- package/dist/accessibility/keyboard-nav.js +86 -0
- package/dist/accessibility/keyboard-nav.js.map +1 -0
- package/dist/accessibility/semantic-html.d.ts +76 -0
- package/dist/accessibility/semantic-html.d.ts.map +1 -0
- package/dist/accessibility/semantic-html.js +204 -0
- package/dist/accessibility/semantic-html.js.map +1 -0
- package/dist/api/client-patterns.d.ts +121 -0
- package/dist/api/client-patterns.d.ts.map +1 -0
- package/dist/api/client-patterns.js +478 -0
- package/dist/api/client-patterns.js.map +1 -0
- package/dist/api/error-format.d.ts +140 -0
- package/dist/api/error-format.d.ts.map +1 -0
- package/dist/api/error-format.js +614 -0
- package/dist/api/error-format.js.map +1 -0
- package/dist/api/http-methods.d.ts +255 -0
- package/dist/api/http-methods.d.ts.map +1 -0
- package/dist/api/http-methods.js +890 -0
- package/dist/api/http-methods.js.map +1 -0
- package/dist/api/index.d.ts +16 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +37 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/pagination.d.ts +133 -0
- package/dist/api/pagination.d.ts.map +1 -0
- package/dist/api/pagination.js +521 -0
- package/dist/api/pagination.js.map +1 -0
- package/dist/api/response-envelope.d.ts +261 -0
- package/dist/api/response-envelope.d.ts.map +1 -0
- package/dist/api/response-envelope.js +1050 -0
- package/dist/api/response-envelope.js.map +1 -0
- package/dist/api/retry-patterns.d.ts +117 -0
- package/dist/api/retry-patterns.d.ts.map +1 -0
- package/dist/api/retry-patterns.js +480 -0
- package/dist/api/retry-patterns.js.map +1 -0
- package/dist/api/route-structure.d.ts +128 -0
- package/dist/api/route-structure.d.ts.map +1 -0
- package/dist/api/route-structure.js +738 -0
- package/dist/api/route-structure.js.map +1 -0
- package/dist/auth/audit-logging.d.ts +80 -0
- package/dist/auth/audit-logging.d.ts.map +1 -0
- package/dist/auth/audit-logging.js +370 -0
- package/dist/auth/audit-logging.js.map +1 -0
- package/dist/auth/index.d.ts +33 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +49 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/middleware-usage.d.ts +65 -0
- package/dist/auth/middleware-usage.d.ts.map +1 -0
- package/dist/auth/middleware-usage.js +192 -0
- package/dist/auth/middleware-usage.js.map +1 -0
- package/dist/auth/permission-checks.d.ts +60 -0
- package/dist/auth/permission-checks.d.ts.map +1 -0
- package/dist/auth/permission-checks.js +159 -0
- package/dist/auth/permission-checks.js.map +1 -0
- package/dist/auth/rbac-patterns.d.ts +68 -0
- package/dist/auth/rbac-patterns.d.ts.map +1 -0
- package/dist/auth/rbac-patterns.js +143 -0
- package/dist/auth/rbac-patterns.js.map +1 -0
- package/dist/auth/resource-ownership.d.ts +77 -0
- package/dist/auth/resource-ownership.d.ts.map +1 -0
- package/dist/auth/resource-ownership.js +324 -0
- package/dist/auth/resource-ownership.js.map +1 -0
- package/dist/auth/token-handling.d.ts +64 -0
- package/dist/auth/token-handling.d.ts.map +1 -0
- package/dist/auth/token-handling.js +151 -0
- package/dist/auth/token-handling.js.map +1 -0
- package/dist/base/ast-detector.d.ts +421 -0
- package/dist/base/ast-detector.d.ts.map +1 -0
- package/dist/base/ast-detector.js +699 -0
- package/dist/base/ast-detector.js.map +1 -0
- package/dist/base/base-detector.d.ts +366 -0
- package/dist/base/base-detector.d.ts.map +1 -0
- package/dist/base/base-detector.js +170 -0
- package/dist/base/base-detector.js.map +1 -0
- package/dist/base/index.d.ts +12 -0
- package/dist/base/index.d.ts.map +1 -0
- package/dist/base/index.js +17 -0
- package/dist/base/index.js.map +1 -0
- package/dist/base/regex-detector.d.ts +421 -0
- package/dist/base/regex-detector.d.ts.map +1 -0
- package/dist/base/regex-detector.js +537 -0
- package/dist/base/regex-detector.js.map +1 -0
- package/dist/base/structural-detector.d.ts +424 -0
- package/dist/base/structural-detector.d.ts.map +1 -0
- package/dist/base/structural-detector.js +731 -0
- package/dist/base/structural-detector.js.map +1 -0
- package/dist/base/types.d.ts +53 -0
- package/dist/base/types.d.ts.map +1 -0
- package/dist/base/types.js +5 -0
- package/dist/base/types.js.map +1 -0
- package/dist/components/component-structure.d.ts +163 -0
- package/dist/components/component-structure.d.ts.map +1 -0
- package/dist/components/component-structure.js +500 -0
- package/dist/components/component-structure.js.map +1 -0
- package/dist/components/composition.d.ts +287 -0
- package/dist/components/composition.d.ts.map +1 -0
- package/dist/components/composition.js +1123 -0
- package/dist/components/composition.js.map +1 -0
- package/dist/components/duplicate-detection.d.ts +251 -0
- package/dist/components/duplicate-detection.d.ts.map +1 -0
- package/dist/components/duplicate-detection.js +804 -0
- package/dist/components/duplicate-detection.js.map +1 -0
- package/dist/components/index.d.ts +16 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +51 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/near-duplicate.d.ts +402 -0
- package/dist/components/near-duplicate.d.ts.map +1 -0
- package/dist/components/near-duplicate.js +1090 -0
- package/dist/components/near-duplicate.js.map +1 -0
- package/dist/components/props-patterns.d.ts +194 -0
- package/dist/components/props-patterns.d.ts.map +1 -0
- package/dist/components/props-patterns.js +795 -0
- package/dist/components/props-patterns.js.map +1 -0
- package/dist/components/ref-forwarding.d.ts +250 -0
- package/dist/components/ref-forwarding.d.ts.map +1 -0
- package/dist/components/ref-forwarding.js +832 -0
- package/dist/components/ref-forwarding.js.map +1 -0
- package/dist/components/state-patterns.d.ts +291 -0
- package/dist/components/state-patterns.d.ts.map +1 -0
- package/dist/components/state-patterns.js +970 -0
- package/dist/components/state-patterns.js.map +1 -0
- package/dist/config/config-validation.d.ts +74 -0
- package/dist/config/config-validation.d.ts.map +1 -0
- package/dist/config/config-validation.js +446 -0
- package/dist/config/config-validation.js.map +1 -0
- package/dist/config/default-values.d.ts +72 -0
- package/dist/config/default-values.d.ts.map +1 -0
- package/dist/config/default-values.js +386 -0
- package/dist/config/default-values.js.map +1 -0
- package/dist/config/env-naming.d.ts +73 -0
- package/dist/config/env-naming.d.ts.map +1 -0
- package/dist/config/env-naming.js +429 -0
- package/dist/config/env-naming.js.map +1 -0
- package/dist/config/environment-detection.d.ts +72 -0
- package/dist/config/environment-detection.d.ts.map +1 -0
- package/dist/config/environment-detection.js +400 -0
- package/dist/config/environment-detection.js.map +1 -0
- package/dist/config/feature-flags.d.ts +72 -0
- package/dist/config/feature-flags.d.ts.map +1 -0
- package/dist/config/feature-flags.js +384 -0
- package/dist/config/feature-flags.js.map +1 -0
- package/dist/config/index.d.ts +27 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +43 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/required-optional.d.ts +71 -0
- package/dist/config/required-optional.d.ts.map +1 -0
- package/dist/config/required-optional.js +344 -0
- package/dist/config/required-optional.js.map +1 -0
- package/dist/data-access/connection-pooling.d.ts +63 -0
- package/dist/data-access/connection-pooling.d.ts.map +1 -0
- package/dist/data-access/connection-pooling.js +297 -0
- package/dist/data-access/connection-pooling.js.map +1 -0
- package/dist/data-access/dto-patterns.d.ts +64 -0
- package/dist/data-access/dto-patterns.d.ts.map +1 -0
- package/dist/data-access/dto-patterns.js +291 -0
- package/dist/data-access/dto-patterns.js.map +1 -0
- package/dist/data-access/index.d.ts +31 -0
- package/dist/data-access/index.d.ts.map +1 -0
- package/dist/data-access/index.js +49 -0
- package/dist/data-access/index.js.map +1 -0
- package/dist/data-access/n-plus-one.d.ts +60 -0
- package/dist/data-access/n-plus-one.d.ts.map +1 -0
- package/dist/data-access/n-plus-one.js +264 -0
- package/dist/data-access/n-plus-one.js.map +1 -0
- package/dist/data-access/query-patterns.d.ts +64 -0
- package/dist/data-access/query-patterns.d.ts.map +1 -0
- package/dist/data-access/query-patterns.js +314 -0
- package/dist/data-access/query-patterns.js.map +1 -0
- package/dist/data-access/repository-pattern.d.ts +62 -0
- package/dist/data-access/repository-pattern.d.ts.map +1 -0
- package/dist/data-access/repository-pattern.js +257 -0
- package/dist/data-access/repository-pattern.js.map +1 -0
- package/dist/data-access/transaction-patterns.d.ts +61 -0
- package/dist/data-access/transaction-patterns.d.ts.map +1 -0
- package/dist/data-access/transaction-patterns.js +277 -0
- package/dist/data-access/transaction-patterns.js.map +1 -0
- package/dist/data-access/validation-patterns.d.ts +62 -0
- package/dist/data-access/validation-patterns.d.ts.map +1 -0
- package/dist/data-access/validation-patterns.js +301 -0
- package/dist/data-access/validation-patterns.js.map +1 -0
- package/dist/documentation/deprecation.d.ts +62 -0
- package/dist/documentation/deprecation.d.ts.map +1 -0
- package/dist/documentation/deprecation.js +83 -0
- package/dist/documentation/deprecation.js.map +1 -0
- package/dist/documentation/example-code.d.ts +64 -0
- package/dist/documentation/example-code.d.ts.map +1 -0
- package/dist/documentation/example-code.js +79 -0
- package/dist/documentation/example-code.js.map +1 -0
- package/dist/documentation/index.d.ts +22 -0
- package/dist/documentation/index.d.ts.map +1 -0
- package/dist/documentation/index.js +19 -0
- package/dist/documentation/index.js.map +1 -0
- package/dist/documentation/jsdoc-patterns.d.ts +72 -0
- package/dist/documentation/jsdoc-patterns.d.ts.map +1 -0
- package/dist/documentation/jsdoc-patterns.js +92 -0
- package/dist/documentation/jsdoc-patterns.js.map +1 -0
- package/dist/documentation/readme-structure.d.ts +67 -0
- package/dist/documentation/readme-structure.d.ts.map +1 -0
- package/dist/documentation/readme-structure.js +76 -0
- package/dist/documentation/readme-structure.js.map +1 -0
- package/dist/documentation/todo-patterns.d.ts +67 -0
- package/dist/documentation/todo-patterns.d.ts.map +1 -0
- package/dist/documentation/todo-patterns.js +73 -0
- package/dist/documentation/todo-patterns.js.map +1 -0
- package/dist/errors/async-errors.d.ts +72 -0
- package/dist/errors/async-errors.d.ts.map +1 -0
- package/dist/errors/async-errors.js +214 -0
- package/dist/errors/async-errors.js.map +1 -0
- package/dist/errors/circuit-breaker.d.ts +53 -0
- package/dist/errors/circuit-breaker.d.ts.map +1 -0
- package/dist/errors/circuit-breaker.js +241 -0
- package/dist/errors/circuit-breaker.js.map +1 -0
- package/dist/errors/error-codes.d.ts +73 -0
- package/dist/errors/error-codes.d.ts.map +1 -0
- package/dist/errors/error-codes.js +211 -0
- package/dist/errors/error-codes.js.map +1 -0
- package/dist/errors/error-logging.d.ts +73 -0
- package/dist/errors/error-logging.d.ts.map +1 -0
- package/dist/errors/error-logging.js +256 -0
- package/dist/errors/error-logging.js.map +1 -0
- package/dist/errors/error-propagation.d.ts +73 -0
- package/dist/errors/error-propagation.d.ts.map +1 -0
- package/dist/errors/error-propagation.js +244 -0
- package/dist/errors/error-propagation.js.map +1 -0
- package/dist/errors/exception-hierarchy.d.ts +75 -0
- package/dist/errors/exception-hierarchy.d.ts.map +1 -0
- package/dist/errors/exception-hierarchy.js +259 -0
- package/dist/errors/exception-hierarchy.js.map +1 -0
- package/dist/errors/index.d.ts +31 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +49 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/try-catch-placement.d.ts +73 -0
- package/dist/errors/try-catch-placement.d.ts.map +1 -0
- package/dist/errors/try-catch-placement.js +214 -0
- package/dist/errors/try-catch-placement.js.map +1 -0
- package/dist/index.d.ts +221 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +245 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/context-fields.d.ts +48 -0
- package/dist/logging/context-fields.d.ts.map +1 -0
- package/dist/logging/context-fields.js +160 -0
- package/dist/logging/context-fields.js.map +1 -0
- package/dist/logging/correlation-ids.d.ts +44 -0
- package/dist/logging/correlation-ids.d.ts.map +1 -0
- package/dist/logging/correlation-ids.js +144 -0
- package/dist/logging/correlation-ids.js.map +1 -0
- package/dist/logging/health-checks.d.ts +45 -0
- package/dist/logging/health-checks.d.ts.map +1 -0
- package/dist/logging/health-checks.js +165 -0
- package/dist/logging/health-checks.js.map +1 -0
- package/dist/logging/index.d.ts +31 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +49 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/log-levels.d.ts +46 -0
- package/dist/logging/log-levels.d.ts.map +1 -0
- package/dist/logging/log-levels.js +178 -0
- package/dist/logging/log-levels.js.map +1 -0
- package/dist/logging/metric-naming.d.ts +46 -0
- package/dist/logging/metric-naming.d.ts.map +1 -0
- package/dist/logging/metric-naming.js +157 -0
- package/dist/logging/metric-naming.js.map +1 -0
- package/dist/logging/pii-redaction.d.ts +44 -0
- package/dist/logging/pii-redaction.d.ts.map +1 -0
- package/dist/logging/pii-redaction.js +166 -0
- package/dist/logging/pii-redaction.js.map +1 -0
- package/dist/logging/structured-format.d.ts +53 -0
- package/dist/logging/structured-format.d.ts.map +1 -0
- package/dist/logging/structured-format.js +235 -0
- package/dist/logging/structured-format.js.map +1 -0
- package/dist/performance/bundle-size.d.ts +79 -0
- package/dist/performance/bundle-size.d.ts.map +1 -0
- package/dist/performance/bundle-size.js +276 -0
- package/dist/performance/bundle-size.js.map +1 -0
- package/dist/performance/caching-patterns.d.ts +78 -0
- package/dist/performance/caching-patterns.d.ts.map +1 -0
- package/dist/performance/caching-patterns.js +257 -0
- package/dist/performance/caching-patterns.js.map +1 -0
- package/dist/performance/code-splitting.d.ts +86 -0
- package/dist/performance/code-splitting.d.ts.map +1 -0
- package/dist/performance/code-splitting.js +447 -0
- package/dist/performance/code-splitting.js.map +1 -0
- package/dist/performance/debounce-throttle.d.ts +75 -0
- package/dist/performance/debounce-throttle.d.ts.map +1 -0
- package/dist/performance/debounce-throttle.js +232 -0
- package/dist/performance/debounce-throttle.js.map +1 -0
- package/dist/performance/index.d.ts +28 -0
- package/dist/performance/index.d.ts.map +1 -0
- package/dist/performance/index.js +39 -0
- package/dist/performance/index.js.map +1 -0
- package/dist/performance/lazy-loading.d.ts +75 -0
- package/dist/performance/lazy-loading.d.ts.map +1 -0
- package/dist/performance/lazy-loading.js +233 -0
- package/dist/performance/lazy-loading.js.map +1 -0
- package/dist/performance/memoization.d.ts +75 -0
- package/dist/performance/memoization.d.ts.map +1 -0
- package/dist/performance/memoization.js +251 -0
- package/dist/performance/memoization.js.map +1 -0
- package/dist/registry/detector-registry.d.ts +266 -0
- package/dist/registry/detector-registry.d.ts.map +1 -0
- package/dist/registry/detector-registry.js +526 -0
- package/dist/registry/detector-registry.js.map +1 -0
- package/dist/registry/index.d.ts +10 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +10 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/loader.d.ts +232 -0
- package/dist/registry/loader.d.ts.map +1 -0
- package/dist/registry/loader.js +419 -0
- package/dist/registry/loader.js.map +1 -0
- package/dist/registry/types.d.ts +111 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +19 -0
- package/dist/registry/types.js.map +1 -0
- package/dist/security/csp-headers.d.ts +78 -0
- package/dist/security/csp-headers.d.ts.map +1 -0
- package/dist/security/csp-headers.js +401 -0
- package/dist/security/csp-headers.js.map +1 -0
- package/dist/security/csrf-protection.d.ts +72 -0
- package/dist/security/csrf-protection.d.ts.map +1 -0
- package/dist/security/csrf-protection.js +344 -0
- package/dist/security/csrf-protection.js.map +1 -0
- package/dist/security/index.d.ts +30 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +48 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/input-sanitization.d.ts +74 -0
- package/dist/security/input-sanitization.d.ts.map +1 -0
- package/dist/security/input-sanitization.js +373 -0
- package/dist/security/input-sanitization.js.map +1 -0
- package/dist/security/rate-limiting.d.ts +81 -0
- package/dist/security/rate-limiting.d.ts.map +1 -0
- package/dist/security/rate-limiting.js +535 -0
- package/dist/security/rate-limiting.js.map +1 -0
- package/dist/security/secret-management.d.ts +83 -0
- package/dist/security/secret-management.d.ts.map +1 -0
- package/dist/security/secret-management.js +547 -0
- package/dist/security/secret-management.js.map +1 -0
- package/dist/security/sql-injection.d.ts +76 -0
- package/dist/security/sql-injection.d.ts.map +1 -0
- package/dist/security/sql-injection.js +383 -0
- package/dist/security/sql-injection.js.map +1 -0
- package/dist/security/xss-prevention.d.ts +80 -0
- package/dist/security/xss-prevention.d.ts.map +1 -0
- package/dist/security/xss-prevention.js +416 -0
- package/dist/security/xss-prevention.js.map +1 -0
- package/dist/structural/barrel-exports.d.ts +178 -0
- package/dist/structural/barrel-exports.d.ts.map +1 -0
- package/dist/structural/barrel-exports.js +553 -0
- package/dist/structural/barrel-exports.js.map +1 -0
- package/dist/structural/circular-deps.d.ts +140 -0
- package/dist/structural/circular-deps.d.ts.map +1 -0
- package/dist/structural/circular-deps.js +422 -0
- package/dist/structural/circular-deps.js.map +1 -0
- package/dist/structural/co-location.d.ts +202 -0
- package/dist/structural/co-location.d.ts.map +1 -0
- package/dist/structural/co-location.js +640 -0
- package/dist/structural/co-location.js.map +1 -0
- package/dist/structural/directory-structure.d.ts +151 -0
- package/dist/structural/directory-structure.d.ts.map +1 -0
- package/dist/structural/directory-structure.js +457 -0
- package/dist/structural/directory-structure.js.map +1 -0
- package/dist/structural/file-naming.d.ts +61 -0
- package/dist/structural/file-naming.d.ts.map +1 -0
- package/dist/structural/file-naming.js +231 -0
- package/dist/structural/file-naming.js.map +1 -0
- package/dist/structural/import-ordering.d.ts +212 -0
- package/dist/structural/import-ordering.d.ts.map +1 -0
- package/dist/structural/import-ordering.js +821 -0
- package/dist/structural/import-ordering.js.map +1 -0
- package/dist/structural/index.d.ts +23 -0
- package/dist/structural/index.d.ts.map +1 -0
- package/dist/structural/index.js +26 -0
- package/dist/structural/index.js.map +1 -0
- package/dist/structural/module-boundaries.d.ts +164 -0
- package/dist/structural/module-boundaries.d.ts.map +1 -0
- package/dist/structural/module-boundaries.js +616 -0
- package/dist/structural/module-boundaries.js.map +1 -0
- package/dist/structural/package-boundaries.d.ts +182 -0
- package/dist/structural/package-boundaries.d.ts.map +1 -0
- package/dist/structural/package-boundaries.js +602 -0
- package/dist/structural/package-boundaries.js.map +1 -0
- package/dist/styling/class-naming.d.ts +263 -0
- package/dist/styling/class-naming.d.ts.map +1 -0
- package/dist/styling/class-naming.js +892 -0
- package/dist/styling/class-naming.js.map +1 -0
- package/dist/styling/color-usage.d.ts +213 -0
- package/dist/styling/color-usage.d.ts.map +1 -0
- package/dist/styling/color-usage.js +732 -0
- package/dist/styling/color-usage.js.map +1 -0
- package/dist/styling/design-tokens.d.ts +212 -0
- package/dist/styling/design-tokens.d.ts.map +1 -0
- package/dist/styling/design-tokens.js +748 -0
- package/dist/styling/design-tokens.js.map +1 -0
- package/dist/styling/index.d.ts +16 -0
- package/dist/styling/index.d.ts.map +1 -0
- package/dist/styling/index.js +56 -0
- package/dist/styling/index.js.map +1 -0
- package/dist/styling/responsive.d.ts +304 -0
- package/dist/styling/responsive.d.ts.map +1 -0
- package/dist/styling/responsive.js +888 -0
- package/dist/styling/responsive.js.map +1 -0
- package/dist/styling/spacing-scale.d.ts +248 -0
- package/dist/styling/spacing-scale.d.ts.map +1 -0
- package/dist/styling/spacing-scale.js +865 -0
- package/dist/styling/spacing-scale.js.map +1 -0
- package/dist/styling/tailwind-patterns.d.ts +305 -0
- package/dist/styling/tailwind-patterns.d.ts.map +1 -0
- package/dist/styling/tailwind-patterns.js +1181 -0
- package/dist/styling/tailwind-patterns.js.map +1 -0
- package/dist/styling/typography.d.ts +281 -0
- package/dist/styling/typography.d.ts.map +1 -0
- package/dist/styling/typography.js +1004 -0
- package/dist/styling/typography.js.map +1 -0
- package/dist/styling/z-index-scale.d.ts +270 -0
- package/dist/styling/z-index-scale.d.ts.map +1 -0
- package/dist/styling/z-index-scale.js +714 -0
- package/dist/styling/z-index-scale.js.map +1 -0
- package/dist/testing/co-location.d.ts +42 -0
- package/dist/testing/co-location.d.ts.map +1 -0
- package/dist/testing/co-location.js +134 -0
- package/dist/testing/co-location.js.map +1 -0
- package/dist/testing/describe-naming.d.ts +47 -0
- package/dist/testing/describe-naming.d.ts.map +1 -0
- package/dist/testing/describe-naming.js +150 -0
- package/dist/testing/describe-naming.js.map +1 -0
- package/dist/testing/file-naming.d.ts +44 -0
- package/dist/testing/file-naming.d.ts.map +1 -0
- package/dist/testing/file-naming.js +131 -0
- package/dist/testing/file-naming.js.map +1 -0
- package/dist/testing/fixture-patterns.d.ts +52 -0
- package/dist/testing/fixture-patterns.d.ts.map +1 -0
- package/dist/testing/fixture-patterns.js +228 -0
- package/dist/testing/fixture-patterns.js.map +1 -0
- package/dist/testing/index.d.ts +31 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +49 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mock-patterns.d.ts +53 -0
- package/dist/testing/mock-patterns.d.ts.map +1 -0
- package/dist/testing/mock-patterns.js +264 -0
- package/dist/testing/mock-patterns.js.map +1 -0
- package/dist/testing/setup-teardown.d.ts +55 -0
- package/dist/testing/setup-teardown.d.ts.map +1 -0
- package/dist/testing/setup-teardown.js +262 -0
- package/dist/testing/setup-teardown.js.map +1 -0
- package/dist/testing/test-structure.d.ts +51 -0
- package/dist/testing/test-structure.d.ts.map +1 -0
- package/dist/testing/test-structure.js +225 -0
- package/dist/testing/test-structure.js.map +1 -0
- package/dist/types/any-usage.d.ts +99 -0
- package/dist/types/any-usage.d.ts.map +1 -0
- package/dist/types/any-usage.js +641 -0
- package/dist/types/any-usage.js.map +1 -0
- package/dist/types/file-location.d.ts +76 -0
- package/dist/types/file-location.d.ts.map +1 -0
- package/dist/types/file-location.js +395 -0
- package/dist/types/file-location.js.map +1 -0
- package/dist/types/generic-patterns.d.ts +97 -0
- package/dist/types/generic-patterns.d.ts.map +1 -0
- package/dist/types/generic-patterns.js +615 -0
- package/dist/types/generic-patterns.js.map +1 -0
- package/dist/types/index.d.ts +31 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +43 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/interface-vs-type.d.ts +81 -0
- package/dist/types/interface-vs-type.d.ts.map +1 -0
- package/dist/types/interface-vs-type.js +440 -0
- package/dist/types/interface-vs-type.js.map +1 -0
- package/dist/types/naming-conventions.d.ts +84 -0
- package/dist/types/naming-conventions.d.ts.map +1 -0
- package/dist/types/naming-conventions.js +455 -0
- package/dist/types/naming-conventions.js.map +1 -0
- package/dist/types/type-assertions.d.ts +98 -0
- package/dist/types/type-assertions.d.ts.map +1 -0
- package/dist/types/type-assertions.js +639 -0
- package/dist/types/type-assertions.js.map +1 -0
- package/dist/types/utility-types.d.ts +110 -0
- package/dist/types/utility-types.d.ts.map +1 -0
- package/dist/types/utility-types.js +547 -0
- package/dist/types/utility-types.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Patterns Detector - State management pattern detection
|
|
3
|
+
*
|
|
4
|
+
* Detects local vs global state usage patterns in React components.
|
|
5
|
+
* Identifies state management inconsistencies and suggests improvements.
|
|
6
|
+
*
|
|
7
|
+
* @requirements 8.5 - THE Component_Detector SHALL detect state management patterns (local vs global)
|
|
8
|
+
*/
|
|
9
|
+
import { ASTDetector } from '../base/index.js';
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Constants
|
|
12
|
+
// ============================================================================
|
|
13
|
+
/**
|
|
14
|
+
* Default configuration for state pattern detection
|
|
15
|
+
*/
|
|
16
|
+
export const DEFAULT_STATE_PATTERN_CONFIG = {
|
|
17
|
+
propDrillingThreshold: 3,
|
|
18
|
+
detectServerState: true,
|
|
19
|
+
flagMixedPatterns: true,
|
|
20
|
+
maxLocalStateVariables: 5,
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Redux hook patterns
|
|
24
|
+
*/
|
|
25
|
+
export const REDUX_HOOKS = ['useSelector', 'useDispatch', 'useStore'];
|
|
26
|
+
/**
|
|
27
|
+
* Zustand hook patterns
|
|
28
|
+
*/
|
|
29
|
+
export const ZUSTAND_PATTERNS = [
|
|
30
|
+
/use[A-Z][a-zA-Z]*Store/, // useUserStore, useCartStore, etc.
|
|
31
|
+
/create\s*\(/, // create() from zustand
|
|
32
|
+
];
|
|
33
|
+
/**
|
|
34
|
+
* Jotai patterns
|
|
35
|
+
*/
|
|
36
|
+
export const JOTAI_HOOKS = ['useAtom', 'useAtomValue', 'useSetAtom'];
|
|
37
|
+
/**
|
|
38
|
+
* Recoil patterns
|
|
39
|
+
*/
|
|
40
|
+
export const RECOIL_HOOKS = ['useRecoilState', 'useRecoilValue', 'useSetRecoilState', 'useRecoilCallback'];
|
|
41
|
+
/**
|
|
42
|
+
* React Query / TanStack Query patterns
|
|
43
|
+
*/
|
|
44
|
+
export const REACT_QUERY_HOOKS = ['useQuery', 'useMutation', 'useInfiniteQuery', 'useQueries'];
|
|
45
|
+
/**
|
|
46
|
+
* SWR patterns
|
|
47
|
+
*/
|
|
48
|
+
export const SWR_HOOKS = ['useSWR', 'useSWRMutation', 'useSWRInfinite'];
|
|
49
|
+
/**
|
|
50
|
+
* MobX patterns
|
|
51
|
+
*/
|
|
52
|
+
export const MOBX_PATTERNS = ['observer', 'useObserver', 'useLocalObservable'];
|
|
53
|
+
/**
|
|
54
|
+
* Valtio patterns
|
|
55
|
+
*/
|
|
56
|
+
export const VALTIO_HOOKS = ['useSnapshot', 'useProxy'];
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// Helper Functions - Component Detection
|
|
59
|
+
// ============================================================================
|
|
60
|
+
/**
|
|
61
|
+
* Check if a node represents a React component
|
|
62
|
+
*/
|
|
63
|
+
export function isReactComponent(node, content) {
|
|
64
|
+
if (node.type === 'function_declaration' ||
|
|
65
|
+
node.type === 'arrow_function' ||
|
|
66
|
+
node.type === 'function_expression') {
|
|
67
|
+
const name = getComponentName(node, content);
|
|
68
|
+
if (!name || !/^[A-Z]/.test(name)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const nodeText = node.text;
|
|
72
|
+
return nodeText.includes('<') && (nodeText.includes('/>') || nodeText.includes('</'));
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get the component name from a node
|
|
78
|
+
*/
|
|
79
|
+
export function getComponentName(node, content) {
|
|
80
|
+
if (node.type === 'function_declaration') {
|
|
81
|
+
const nameNode = node.children.find(c => c.type === 'identifier');
|
|
82
|
+
return nameNode?.text;
|
|
83
|
+
}
|
|
84
|
+
const lines = content.split('\n');
|
|
85
|
+
const line = lines[node.startPosition.row];
|
|
86
|
+
if (line) {
|
|
87
|
+
const match = line.match(/(?:const|let|var|export\s+(?:const|let|var)?)\s+([A-Z][a-zA-Z0-9]*)\s*[=:]/);
|
|
88
|
+
if (match && match[1]) {
|
|
89
|
+
return match[1];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// Helper Functions - Local State Detection
|
|
96
|
+
// ============================================================================
|
|
97
|
+
/**
|
|
98
|
+
* Detect useState usage in a component
|
|
99
|
+
*/
|
|
100
|
+
export function detectUseState(nodeText) {
|
|
101
|
+
const results = [];
|
|
102
|
+
// Match: const [value, setValue] = useState(initial)
|
|
103
|
+
const useStateMatches = nodeText.matchAll(/const\s+\[\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*,\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\]\s*=\s*useState\s*(?:<[^>]*>)?\s*\(([^)]*)\)/g);
|
|
104
|
+
for (const match of useStateMatches) {
|
|
105
|
+
if (match[1] && match[2]) {
|
|
106
|
+
const beforeMatch = nodeText.slice(0, match.index);
|
|
107
|
+
const line = beforeMatch.split('\n').length;
|
|
108
|
+
results.push({
|
|
109
|
+
type: 'local',
|
|
110
|
+
pattern: 'useState',
|
|
111
|
+
variableNames: [match[1], match[2]],
|
|
112
|
+
line,
|
|
113
|
+
column: 1,
|
|
114
|
+
derivedFromProps: match[3]?.includes('props') || false,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return results;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Detect useReducer usage in a component
|
|
122
|
+
*/
|
|
123
|
+
export function detectUseReducer(nodeText) {
|
|
124
|
+
const results = [];
|
|
125
|
+
// Match: const [state, dispatch] = useReducer(reducer, initial)
|
|
126
|
+
const useReducerMatches = nodeText.matchAll(/const\s+\[\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*,\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\]\s*=\s*useReducer\s*\(/g);
|
|
127
|
+
for (const match of useReducerMatches) {
|
|
128
|
+
if (match[1] && match[2]) {
|
|
129
|
+
const beforeMatch = nodeText.slice(0, match.index);
|
|
130
|
+
const line = beforeMatch.split('\n').length;
|
|
131
|
+
results.push({
|
|
132
|
+
type: 'local',
|
|
133
|
+
pattern: 'useReducer',
|
|
134
|
+
variableNames: [match[1], match[2]],
|
|
135
|
+
line,
|
|
136
|
+
column: 1,
|
|
137
|
+
derivedFromProps: false,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return results;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Detect useRef usage for mutable values in a component
|
|
145
|
+
*/
|
|
146
|
+
export function detectUseRef(nodeText) {
|
|
147
|
+
const results = [];
|
|
148
|
+
// Match: const ref = useRef(initial)
|
|
149
|
+
const useRefMatches = nodeText.matchAll(/const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*useRef\s*(?:<[^>]*>)?\s*\(([^)]*)\)/g);
|
|
150
|
+
for (const match of useRefMatches) {
|
|
151
|
+
if (match[1]) {
|
|
152
|
+
const beforeMatch = nodeText.slice(0, match.index);
|
|
153
|
+
const line = beforeMatch.split('\n').length;
|
|
154
|
+
// Only count as state if it's used for mutable values (not DOM refs)
|
|
155
|
+
const refName = match[1];
|
|
156
|
+
const initialValue = match[2] || '';
|
|
157
|
+
const isDOMRef = initialValue === 'null' && nodeText.includes(`ref={${refName}}`);
|
|
158
|
+
if (!isDOMRef) {
|
|
159
|
+
results.push({
|
|
160
|
+
type: 'local',
|
|
161
|
+
pattern: 'useRef',
|
|
162
|
+
variableNames: [refName],
|
|
163
|
+
line,
|
|
164
|
+
column: 1,
|
|
165
|
+
derivedFromProps: initialValue.includes('props'),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return results;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Detect all local state patterns in a component
|
|
174
|
+
*/
|
|
175
|
+
export function detectLocalState(nodeText) {
|
|
176
|
+
return [
|
|
177
|
+
...detectUseState(nodeText),
|
|
178
|
+
...detectUseReducer(nodeText),
|
|
179
|
+
...detectUseRef(nodeText),
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
// ============================================================================
|
|
183
|
+
// Helper Functions - Global State Detection
|
|
184
|
+
// ============================================================================
|
|
185
|
+
/**
|
|
186
|
+
* Detect useContext usage in a component
|
|
187
|
+
*/
|
|
188
|
+
export function detectUseContext(nodeText) {
|
|
189
|
+
const results = [];
|
|
190
|
+
// Match: const value = useContext(SomeContext)
|
|
191
|
+
const useContextMatches = nodeText.matchAll(/const\s+(?:\{([^}]+)\}|([a-zA-Z_$][a-zA-Z0-9_$]*))\s*=\s*useContext\s*\(\s*([A-Z][a-zA-Z0-9]*(?:Context)?)\s*\)/g);
|
|
192
|
+
for (const match of useContextMatches) {
|
|
193
|
+
const beforeMatch = nodeText.slice(0, match.index);
|
|
194
|
+
const line = beforeMatch.split('\n').length;
|
|
195
|
+
// Handle destructured or single variable
|
|
196
|
+
let variableNames = [];
|
|
197
|
+
if (match[1]) {
|
|
198
|
+
// Destructured: const { value1, value2 } = useContext(...)
|
|
199
|
+
variableNames = match[1].split(',').map(v => v.trim().split(':')[0]?.trim() || '').filter(v => v);
|
|
200
|
+
}
|
|
201
|
+
else if (match[2]) {
|
|
202
|
+
// Single variable: const ctx = useContext(...)
|
|
203
|
+
variableNames = [match[2]];
|
|
204
|
+
}
|
|
205
|
+
if (variableNames.length > 0) {
|
|
206
|
+
results.push({
|
|
207
|
+
type: 'global',
|
|
208
|
+
pattern: 'useContext',
|
|
209
|
+
variableNames,
|
|
210
|
+
line,
|
|
211
|
+
column: 1,
|
|
212
|
+
derivedFromProps: false,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return results;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Detect Redux hooks usage in a component
|
|
220
|
+
*/
|
|
221
|
+
export function detectRedux(nodeText) {
|
|
222
|
+
const results = [];
|
|
223
|
+
// Detect useSelector
|
|
224
|
+
const useSelectorMatches = nodeText.matchAll(/const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*useSelector\s*\(/g);
|
|
225
|
+
for (const match of useSelectorMatches) {
|
|
226
|
+
if (match[1]) {
|
|
227
|
+
const beforeMatch = nodeText.slice(0, match.index);
|
|
228
|
+
const line = beforeMatch.split('\n').length;
|
|
229
|
+
results.push({
|
|
230
|
+
type: 'global',
|
|
231
|
+
pattern: 'redux',
|
|
232
|
+
variableNames: [match[1]],
|
|
233
|
+
line,
|
|
234
|
+
column: 1,
|
|
235
|
+
derivedFromProps: false,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Detect useDispatch
|
|
240
|
+
const useDispatchMatches = nodeText.matchAll(/const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*useDispatch\s*\(\s*\)/g);
|
|
241
|
+
for (const match of useDispatchMatches) {
|
|
242
|
+
if (match[1]) {
|
|
243
|
+
const beforeMatch = nodeText.slice(0, match.index);
|
|
244
|
+
const line = beforeMatch.split('\n').length;
|
|
245
|
+
results.push({
|
|
246
|
+
type: 'global',
|
|
247
|
+
pattern: 'redux',
|
|
248
|
+
variableNames: [match[1]],
|
|
249
|
+
line,
|
|
250
|
+
column: 1,
|
|
251
|
+
derivedFromProps: false,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return results;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Detect Zustand store hooks usage in a component
|
|
259
|
+
*/
|
|
260
|
+
export function detectZustand(nodeText) {
|
|
261
|
+
const results = [];
|
|
262
|
+
// Match: const value = useXxxStore() or const { a, b } = useXxxStore()
|
|
263
|
+
const zustandMatches = nodeText.matchAll(/const\s+(?:\{([^}]+)\}|([a-zA-Z_$][a-zA-Z0-9_$]*))\s*=\s*(use[A-Z][a-zA-Z]*Store)\s*\(/g);
|
|
264
|
+
for (const match of zustandMatches) {
|
|
265
|
+
const beforeMatch = nodeText.slice(0, match.index);
|
|
266
|
+
const line = beforeMatch.split('\n').length;
|
|
267
|
+
let variableNames = [];
|
|
268
|
+
if (match[1]) {
|
|
269
|
+
variableNames = match[1].split(',').map(v => v.trim().split(':')[0]?.trim() || '').filter(v => v);
|
|
270
|
+
}
|
|
271
|
+
else if (match[2]) {
|
|
272
|
+
variableNames = [match[2]];
|
|
273
|
+
}
|
|
274
|
+
if (variableNames.length > 0) {
|
|
275
|
+
results.push({
|
|
276
|
+
type: 'global',
|
|
277
|
+
pattern: 'zustand',
|
|
278
|
+
variableNames,
|
|
279
|
+
line,
|
|
280
|
+
column: 1,
|
|
281
|
+
derivedFromProps: false,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return results;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Detect Jotai atoms usage in a component
|
|
289
|
+
*/
|
|
290
|
+
export function detectJotai(nodeText) {
|
|
291
|
+
const results = [];
|
|
292
|
+
// Match useAtom, useAtomValue, useSetAtom
|
|
293
|
+
for (const hook of JOTAI_HOOKS) {
|
|
294
|
+
const pattern = new RegExp(`const\\s+(?:\\[([^\\]]+)\\]|([a-zA-Z_$][a-zA-Z0-9_$]*))\\s*=\\s*${hook}\\s*\\(`, 'g');
|
|
295
|
+
const matches = nodeText.matchAll(pattern);
|
|
296
|
+
for (const match of matches) {
|
|
297
|
+
const beforeMatch = nodeText.slice(0, match.index);
|
|
298
|
+
const line = beforeMatch.split('\n').length;
|
|
299
|
+
let variableNames = [];
|
|
300
|
+
if (match[1]) {
|
|
301
|
+
variableNames = match[1].split(',').map(v => v.trim()).filter(v => v);
|
|
302
|
+
}
|
|
303
|
+
else if (match[2]) {
|
|
304
|
+
variableNames = [match[2]];
|
|
305
|
+
}
|
|
306
|
+
if (variableNames.length > 0) {
|
|
307
|
+
results.push({
|
|
308
|
+
type: 'global',
|
|
309
|
+
pattern: 'jotai',
|
|
310
|
+
variableNames,
|
|
311
|
+
line,
|
|
312
|
+
column: 1,
|
|
313
|
+
derivedFromProps: false,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return results;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Detect Recoil atoms usage in a component
|
|
322
|
+
*/
|
|
323
|
+
export function detectRecoil(nodeText) {
|
|
324
|
+
const results = [];
|
|
325
|
+
for (const hook of RECOIL_HOOKS) {
|
|
326
|
+
const pattern = new RegExp(`const\\s+(?:\\[([^\\]]+)\\]|([a-zA-Z_$][a-zA-Z0-9_$]*))\\s*=\\s*${hook}\\s*\\(`, 'g');
|
|
327
|
+
const matches = nodeText.matchAll(pattern);
|
|
328
|
+
for (const match of matches) {
|
|
329
|
+
const beforeMatch = nodeText.slice(0, match.index);
|
|
330
|
+
const line = beforeMatch.split('\n').length;
|
|
331
|
+
let variableNames = [];
|
|
332
|
+
if (match[1]) {
|
|
333
|
+
variableNames = match[1].split(',').map(v => v.trim()).filter(v => v);
|
|
334
|
+
}
|
|
335
|
+
else if (match[2]) {
|
|
336
|
+
variableNames = [match[2]];
|
|
337
|
+
}
|
|
338
|
+
if (variableNames.length > 0) {
|
|
339
|
+
results.push({
|
|
340
|
+
type: 'global',
|
|
341
|
+
pattern: 'recoil',
|
|
342
|
+
variableNames,
|
|
343
|
+
line,
|
|
344
|
+
column: 1,
|
|
345
|
+
derivedFromProps: false,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return results;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Detect React Query / TanStack Query usage in a component
|
|
354
|
+
*/
|
|
355
|
+
export function detectReactQuery(nodeText) {
|
|
356
|
+
const results = [];
|
|
357
|
+
for (const hook of REACT_QUERY_HOOKS) {
|
|
358
|
+
const pattern = new RegExp(`const\\s+(?:\\{([^}]+)\\}|([a-zA-Z_$][a-zA-Z0-9_$]*))\\s*=\\s*${hook}\\s*\\(`, 'g');
|
|
359
|
+
const matches = nodeText.matchAll(pattern);
|
|
360
|
+
for (const match of matches) {
|
|
361
|
+
const beforeMatch = nodeText.slice(0, match.index);
|
|
362
|
+
const line = beforeMatch.split('\n').length;
|
|
363
|
+
let variableNames = [];
|
|
364
|
+
if (match[1]) {
|
|
365
|
+
variableNames = match[1].split(',').map(v => v.trim().split(':')[0]?.trim() || '').filter(v => v);
|
|
366
|
+
}
|
|
367
|
+
else if (match[2]) {
|
|
368
|
+
variableNames = [match[2]];
|
|
369
|
+
}
|
|
370
|
+
if (variableNames.length > 0) {
|
|
371
|
+
results.push({
|
|
372
|
+
type: 'global',
|
|
373
|
+
pattern: 'react-query',
|
|
374
|
+
variableNames,
|
|
375
|
+
line,
|
|
376
|
+
column: 1,
|
|
377
|
+
derivedFromProps: false,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return results;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Detect SWR usage in a component
|
|
386
|
+
*/
|
|
387
|
+
export function detectSWR(nodeText) {
|
|
388
|
+
const results = [];
|
|
389
|
+
for (const hook of SWR_HOOKS) {
|
|
390
|
+
const pattern = new RegExp(`const\\s+(?:\\{([^}]+)\\}|([a-zA-Z_$][a-zA-Z0-9_$]*))\\s*=\\s*${hook}\\s*\\(`, 'g');
|
|
391
|
+
const matches = nodeText.matchAll(pattern);
|
|
392
|
+
for (const match of matches) {
|
|
393
|
+
const beforeMatch = nodeText.slice(0, match.index);
|
|
394
|
+
const line = beforeMatch.split('\n').length;
|
|
395
|
+
let variableNames = [];
|
|
396
|
+
if (match[1]) {
|
|
397
|
+
variableNames = match[1].split(',').map(v => v.trim().split(':')[0]?.trim() || '').filter(v => v);
|
|
398
|
+
}
|
|
399
|
+
else if (match[2]) {
|
|
400
|
+
variableNames = [match[2]];
|
|
401
|
+
}
|
|
402
|
+
if (variableNames.length > 0) {
|
|
403
|
+
results.push({
|
|
404
|
+
type: 'global',
|
|
405
|
+
pattern: 'swr',
|
|
406
|
+
variableNames,
|
|
407
|
+
line,
|
|
408
|
+
column: 1,
|
|
409
|
+
derivedFromProps: false,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return results;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Detect MobX usage in a component
|
|
418
|
+
*/
|
|
419
|
+
export function detectMobX(nodeText) {
|
|
420
|
+
const results = [];
|
|
421
|
+
// Check for observer wrapper
|
|
422
|
+
if (nodeText.includes('observer(') || nodeText.includes('observer<')) {
|
|
423
|
+
results.push({
|
|
424
|
+
type: 'global',
|
|
425
|
+
pattern: 'mobx',
|
|
426
|
+
variableNames: ['observer'],
|
|
427
|
+
line: 1,
|
|
428
|
+
column: 1,
|
|
429
|
+
derivedFromProps: false,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
// Check for useLocalObservable
|
|
433
|
+
const useLocalObservableMatches = nodeText.matchAll(/const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*useLocalObservable\s*\(/g);
|
|
434
|
+
for (const match of useLocalObservableMatches) {
|
|
435
|
+
if (match[1]) {
|
|
436
|
+
const beforeMatch = nodeText.slice(0, match.index);
|
|
437
|
+
const line = beforeMatch.split('\n').length;
|
|
438
|
+
results.push({
|
|
439
|
+
type: 'global',
|
|
440
|
+
pattern: 'mobx',
|
|
441
|
+
variableNames: [match[1]],
|
|
442
|
+
line,
|
|
443
|
+
column: 1,
|
|
444
|
+
derivedFromProps: false,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return results;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Detect Valtio usage in a component
|
|
452
|
+
*/
|
|
453
|
+
export function detectValtio(nodeText) {
|
|
454
|
+
const results = [];
|
|
455
|
+
for (const hook of VALTIO_HOOKS) {
|
|
456
|
+
const pattern = new RegExp(`const\\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\\s*=\\s*${hook}\\s*\\(`, 'g');
|
|
457
|
+
const matches = nodeText.matchAll(pattern);
|
|
458
|
+
for (const match of matches) {
|
|
459
|
+
if (match[1]) {
|
|
460
|
+
const beforeMatch = nodeText.slice(0, match.index);
|
|
461
|
+
const line = beforeMatch.split('\n').length;
|
|
462
|
+
results.push({
|
|
463
|
+
type: 'global',
|
|
464
|
+
pattern: 'valtio',
|
|
465
|
+
variableNames: [match[1]],
|
|
466
|
+
line,
|
|
467
|
+
column: 1,
|
|
468
|
+
derivedFromProps: false,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return results;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Detect all global state patterns in a component
|
|
477
|
+
*/
|
|
478
|
+
export function detectGlobalState(nodeText, config) {
|
|
479
|
+
const results = [
|
|
480
|
+
...detectUseContext(nodeText),
|
|
481
|
+
...detectRedux(nodeText),
|
|
482
|
+
...detectZustand(nodeText),
|
|
483
|
+
...detectJotai(nodeText),
|
|
484
|
+
...detectRecoil(nodeText),
|
|
485
|
+
...detectMobX(nodeText),
|
|
486
|
+
...detectValtio(nodeText),
|
|
487
|
+
];
|
|
488
|
+
// Add server state patterns if configured
|
|
489
|
+
if (config.detectServerState) {
|
|
490
|
+
results.push(...detectReactQuery(nodeText), ...detectSWR(nodeText));
|
|
491
|
+
}
|
|
492
|
+
return results;
|
|
493
|
+
}
|
|
494
|
+
// ============================================================================
|
|
495
|
+
// Helper Functions - Issue Detection
|
|
496
|
+
// ============================================================================
|
|
497
|
+
/**
|
|
498
|
+
* Detect prop drilling patterns
|
|
499
|
+
*/
|
|
500
|
+
export function detectPropDrilling(nodeText, props) {
|
|
501
|
+
const passedDownProps = [];
|
|
502
|
+
// Check if props are passed down to child components
|
|
503
|
+
for (const prop of props) {
|
|
504
|
+
// Match: <Component propName={propName} /> or propName={props.propName}
|
|
505
|
+
const passedDownPattern = new RegExp(`<[A-Z][a-zA-Z0-9]*[^>]*\\s+(?:${prop}|[a-zA-Z_$][a-zA-Z0-9_$]*)\\s*=\\s*\\{\\s*(?:${prop}|props\\.${prop})\\s*\\}`, 'g');
|
|
506
|
+
if (passedDownPattern.test(nodeText)) {
|
|
507
|
+
passedDownProps.push(prop);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return passedDownProps;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Detect state management issues in a component
|
|
514
|
+
*/
|
|
515
|
+
export function detectStateIssues(localState, globalState, passedDownProps, config) {
|
|
516
|
+
const issues = [];
|
|
517
|
+
// Check for mixed state management patterns
|
|
518
|
+
if (config.flagMixedPatterns) {
|
|
519
|
+
const globalPatterns = new Set(globalState.map(s => s.pattern));
|
|
520
|
+
// Exclude server state patterns from mixed pattern detection
|
|
521
|
+
const clientStatePatterns = new Set([...globalPatterns].filter(p => p !== 'react-query' && p !== 'swr'));
|
|
522
|
+
if (clientStatePatterns.size > 1) {
|
|
523
|
+
const patternList = [...clientStatePatterns].join(', ');
|
|
524
|
+
issues.push({
|
|
525
|
+
type: 'mixed-patterns',
|
|
526
|
+
description: `Component uses multiple global state management patterns: ${patternList}`,
|
|
527
|
+
severity: 'warning',
|
|
528
|
+
suggestion: 'Consider standardizing on a single state management solution for consistency',
|
|
529
|
+
line: globalState[0]?.line || 1,
|
|
530
|
+
column: 1,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
// Check for excessive local state
|
|
535
|
+
if (localState.length > config.maxLocalStateVariables) {
|
|
536
|
+
issues.push({
|
|
537
|
+
type: 'local-should-lift',
|
|
538
|
+
description: `Component has ${localState.length} local state variables, which may indicate complex state that should be extracted`,
|
|
539
|
+
severity: 'info',
|
|
540
|
+
suggestion: 'Consider extracting related state into a custom hook or using useReducer',
|
|
541
|
+
line: localState[0]?.line || 1,
|
|
542
|
+
column: 1,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
// Check for potential prop drilling
|
|
546
|
+
if (passedDownProps.length >= config.propDrillingThreshold) {
|
|
547
|
+
issues.push({
|
|
548
|
+
type: 'prop-drilling',
|
|
549
|
+
description: `${passedDownProps.length} props are being passed down to child components`,
|
|
550
|
+
severity: 'info',
|
|
551
|
+
suggestion: 'Consider using Context or a state management library to avoid prop drilling',
|
|
552
|
+
line: 1,
|
|
553
|
+
column: 1,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
// Check for state derived from props (potential issue)
|
|
557
|
+
const derivedState = localState.filter(s => s.derivedFromProps);
|
|
558
|
+
if (derivedState.length > 0) {
|
|
559
|
+
issues.push({
|
|
560
|
+
type: 'global-should-be-local',
|
|
561
|
+
description: `${derivedState.length} state variable(s) are derived from props, which can cause sync issues`,
|
|
562
|
+
severity: 'info',
|
|
563
|
+
suggestion: 'Consider computing derived values directly or using useMemo instead of useState',
|
|
564
|
+
line: derivedState[0]?.line || 1,
|
|
565
|
+
column: 1,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
return issues;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Calculate state complexity score for a component
|
|
572
|
+
*/
|
|
573
|
+
export function calculateComplexityScore(localState, globalState, issues) {
|
|
574
|
+
let score = 0;
|
|
575
|
+
// Base score from state count
|
|
576
|
+
score += localState.length * 1;
|
|
577
|
+
score += globalState.length * 2; // Global state adds more complexity
|
|
578
|
+
// Add complexity for issues
|
|
579
|
+
for (const issue of issues) {
|
|
580
|
+
switch (issue.severity) {
|
|
581
|
+
case 'error':
|
|
582
|
+
score += 5;
|
|
583
|
+
break;
|
|
584
|
+
case 'warning':
|
|
585
|
+
score += 3;
|
|
586
|
+
break;
|
|
587
|
+
case 'info':
|
|
588
|
+
score += 1;
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
// Add complexity for mixed patterns
|
|
593
|
+
const uniquePatterns = new Set([
|
|
594
|
+
...localState.map(s => s.pattern),
|
|
595
|
+
...globalState.map(s => s.pattern),
|
|
596
|
+
]);
|
|
597
|
+
score += (uniquePatterns.size - 1) * 2;
|
|
598
|
+
return score;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Extract props from component signature
|
|
602
|
+
*/
|
|
603
|
+
export function extractPropsFromComponent(nodeText) {
|
|
604
|
+
const props = [];
|
|
605
|
+
// Match destructured props: ({ prop1, prop2, prop3 })
|
|
606
|
+
const destructuringMatch = nodeText.match(/\(\s*\{\s*([^}]+)\s*\}/);
|
|
607
|
+
if (destructuringMatch && destructuringMatch[1]) {
|
|
608
|
+
const propsStr = destructuringMatch[1];
|
|
609
|
+
const propParts = propsStr.split(',');
|
|
610
|
+
for (const part of propParts) {
|
|
611
|
+
const trimmed = part.trim();
|
|
612
|
+
if (trimmed.startsWith('...'))
|
|
613
|
+
continue;
|
|
614
|
+
const propMatch = trimmed.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)/);
|
|
615
|
+
if (propMatch && propMatch[1]) {
|
|
616
|
+
props.push(propMatch[1]);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// Also check for props.propName access
|
|
621
|
+
const directAccessMatches = nodeText.matchAll(/props\.([a-zA-Z_$][a-zA-Z0-9_$]*)/g);
|
|
622
|
+
for (const match of directAccessMatches) {
|
|
623
|
+
if (match[1] && !props.includes(match[1])) {
|
|
624
|
+
props.push(match[1]);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return props;
|
|
628
|
+
}
|
|
629
|
+
// ============================================================================
|
|
630
|
+
// Helper Functions - Analysis
|
|
631
|
+
// ============================================================================
|
|
632
|
+
/**
|
|
633
|
+
* Analyze a single component's state patterns
|
|
634
|
+
*/
|
|
635
|
+
export function analyzeComponentState(node, content, filePath, config) {
|
|
636
|
+
const componentName = getComponentName(node, content);
|
|
637
|
+
if (!componentName) {
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
const nodeText = node.text;
|
|
641
|
+
const localState = detectLocalState(nodeText);
|
|
642
|
+
const globalState = detectGlobalState(nodeText, config);
|
|
643
|
+
const props = extractPropsFromComponent(nodeText);
|
|
644
|
+
const passedDownProps = detectPropDrilling(nodeText, props);
|
|
645
|
+
const issues = detectStateIssues(localState, globalState, passedDownProps, config);
|
|
646
|
+
const complexityScore = calculateComplexityScore(localState, globalState, issues);
|
|
647
|
+
return {
|
|
648
|
+
componentName,
|
|
649
|
+
filePath,
|
|
650
|
+
line: node.startPosition.row + 1,
|
|
651
|
+
column: node.startPosition.column + 1,
|
|
652
|
+
localState,
|
|
653
|
+
globalState,
|
|
654
|
+
issues,
|
|
655
|
+
passedDownProps,
|
|
656
|
+
complexityScore,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Find dominant pattern from a list
|
|
661
|
+
*/
|
|
662
|
+
function findDominantPattern(patterns, excludeValue) {
|
|
663
|
+
const counts = new Map();
|
|
664
|
+
for (const pattern of patterns) {
|
|
665
|
+
if (pattern !== excludeValue) {
|
|
666
|
+
counts.set(pattern, (counts.get(pattern) || 0) + 1);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
let dominant = excludeValue;
|
|
670
|
+
let maxCount = 0;
|
|
671
|
+
for (const [pattern, count] of counts) {
|
|
672
|
+
if (count > maxCount) {
|
|
673
|
+
maxCount = count;
|
|
674
|
+
dominant = pattern;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
const total = patterns.filter(p => p !== excludeValue).length;
|
|
678
|
+
const confidence = total > 0 ? maxCount / total : 0;
|
|
679
|
+
return { dominant, confidence };
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Analyze state patterns across multiple components
|
|
683
|
+
*/
|
|
684
|
+
export function analyzeStatePatterns(components) {
|
|
685
|
+
if (components.length === 0) {
|
|
686
|
+
return {
|
|
687
|
+
components: [],
|
|
688
|
+
dominantLocalPattern: 'none',
|
|
689
|
+
dominantGlobalPattern: 'none',
|
|
690
|
+
confidence: { localPattern: 0, globalPattern: 0 },
|
|
691
|
+
componentsWithIssues: [],
|
|
692
|
+
healthScore: 1.0,
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
// Collect all patterns
|
|
696
|
+
const localPatterns = [];
|
|
697
|
+
const globalPatterns = [];
|
|
698
|
+
for (const comp of components) {
|
|
699
|
+
for (const state of comp.localState) {
|
|
700
|
+
localPatterns.push(state.pattern);
|
|
701
|
+
}
|
|
702
|
+
for (const state of comp.globalState) {
|
|
703
|
+
globalPatterns.push(state.pattern);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
// Find dominant patterns
|
|
707
|
+
const localResult = findDominantPattern(localPatterns, 'none');
|
|
708
|
+
const globalResult = findDominantPattern(globalPatterns, 'none');
|
|
709
|
+
// Find components with issues
|
|
710
|
+
const componentsWithIssues = components.filter(c => c.issues.length > 0);
|
|
711
|
+
// Calculate health score
|
|
712
|
+
const totalIssues = components.reduce((sum, c) => sum + c.issues.length, 0);
|
|
713
|
+
const avgComplexity = components.reduce((sum, c) => sum + c.complexityScore, 0) / components.length;
|
|
714
|
+
const healthScore = Math.max(0, 1 - (totalIssues * 0.1) - (avgComplexity * 0.02));
|
|
715
|
+
return {
|
|
716
|
+
components,
|
|
717
|
+
dominantLocalPattern: localResult.dominant,
|
|
718
|
+
dominantGlobalPattern: globalResult.dominant,
|
|
719
|
+
confidence: {
|
|
720
|
+
localPattern: localResult.confidence,
|
|
721
|
+
globalPattern: globalResult.confidence,
|
|
722
|
+
},
|
|
723
|
+
componentsWithIssues,
|
|
724
|
+
healthScore,
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
// ============================================================================
|
|
728
|
+
// Pattern Description Helpers
|
|
729
|
+
// ============================================================================
|
|
730
|
+
export const LOCAL_PATTERN_DESCRIPTIONS = {
|
|
731
|
+
'useState': 'React useState hook',
|
|
732
|
+
'useReducer': 'React useReducer hook',
|
|
733
|
+
'useRef': 'React useRef for mutable values',
|
|
734
|
+
'class-state': 'Class component state',
|
|
735
|
+
'none': 'no local state',
|
|
736
|
+
};
|
|
737
|
+
export const GLOBAL_PATTERN_DESCRIPTIONS = {
|
|
738
|
+
'useContext': 'React Context API',
|
|
739
|
+
'redux': 'Redux (useSelector/useDispatch)',
|
|
740
|
+
'zustand': 'Zustand store',
|
|
741
|
+
'jotai': 'Jotai atoms',
|
|
742
|
+
'recoil': 'Recoil atoms',
|
|
743
|
+
'react-query': 'React Query / TanStack Query',
|
|
744
|
+
'swr': 'SWR',
|
|
745
|
+
'mobx': 'MobX',
|
|
746
|
+
'valtio': 'Valtio',
|
|
747
|
+
'none': 'no global state',
|
|
748
|
+
};
|
|
749
|
+
// ============================================================================
|
|
750
|
+
// State Pattern Detector Class
|
|
751
|
+
// ============================================================================
|
|
752
|
+
/**
|
|
753
|
+
* Detector for state management patterns
|
|
754
|
+
*
|
|
755
|
+
* Identifies local vs global state usage patterns and detects
|
|
756
|
+
* state management inconsistencies.
|
|
757
|
+
*
|
|
758
|
+
* @requirements 8.5 - THE Component_Detector SHALL detect state management patterns (local vs global)
|
|
759
|
+
*/
|
|
760
|
+
export class StatePatternDetector extends ASTDetector {
|
|
761
|
+
id = 'components/state-patterns';
|
|
762
|
+
category = 'components';
|
|
763
|
+
subcategory = 'state-management';
|
|
764
|
+
name = 'State Pattern Detector';
|
|
765
|
+
description = 'Detects local vs global state usage patterns and identifies state management inconsistencies';
|
|
766
|
+
supportedLanguages = ['typescript', 'javascript'];
|
|
767
|
+
config;
|
|
768
|
+
constructor(config = {}) {
|
|
769
|
+
super();
|
|
770
|
+
this.config = { ...DEFAULT_STATE_PATTERN_CONFIG, ...config };
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Detect state patterns in the project
|
|
774
|
+
*/
|
|
775
|
+
async detect(context) {
|
|
776
|
+
const patterns = [];
|
|
777
|
+
const violations = [];
|
|
778
|
+
// Find all components in the file
|
|
779
|
+
const componentInfos = this.findComponentsInFile(context);
|
|
780
|
+
if (componentInfos.length === 0) {
|
|
781
|
+
return this.createEmptyResult();
|
|
782
|
+
}
|
|
783
|
+
// Analyze patterns across all components
|
|
784
|
+
const analysis = analyzeStatePatterns(componentInfos);
|
|
785
|
+
// Create pattern matches for detected patterns
|
|
786
|
+
if (analysis.dominantLocalPattern !== 'none' && analysis.confidence.localPattern > 0.3) {
|
|
787
|
+
patterns.push(this.createLocalStatePattern(context.file, analysis));
|
|
788
|
+
}
|
|
789
|
+
if (analysis.dominantGlobalPattern !== 'none' && analysis.confidence.globalPattern > 0.3) {
|
|
790
|
+
patterns.push(this.createGlobalStatePattern(context.file, analysis));
|
|
791
|
+
}
|
|
792
|
+
// Generate violations for components with issues
|
|
793
|
+
for (const comp of analysis.componentsWithIssues) {
|
|
794
|
+
if (comp.filePath === context.file) {
|
|
795
|
+
for (const issue of comp.issues) {
|
|
796
|
+
const violation = this.createIssueViolation(context.file, comp, issue);
|
|
797
|
+
violations.push(violation);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
const overallConfidence = Math.max(analysis.confidence.localPattern, analysis.confidence.globalPattern, analysis.healthScore);
|
|
802
|
+
return this.createResult(patterns, violations, overallConfidence);
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Generate a quick fix for state pattern violations
|
|
806
|
+
*/
|
|
807
|
+
generateQuickFix(violation) {
|
|
808
|
+
// Generate quick fixes based on issue type
|
|
809
|
+
if (violation.message.includes('mixed-patterns')) {
|
|
810
|
+
return {
|
|
811
|
+
title: 'Standardize state management',
|
|
812
|
+
kind: 'refactor',
|
|
813
|
+
edit: { changes: {} },
|
|
814
|
+
isPreferred: false,
|
|
815
|
+
confidence: 0.5,
|
|
816
|
+
preview: 'Consider refactoring to use a single state management solution',
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
if (violation.message.includes('prop drilling')) {
|
|
820
|
+
return {
|
|
821
|
+
title: 'Extract to Context',
|
|
822
|
+
kind: 'refactor',
|
|
823
|
+
edit: { changes: {} },
|
|
824
|
+
isPreferred: true,
|
|
825
|
+
confidence: 0.6,
|
|
826
|
+
preview: 'Create a Context provider to avoid prop drilling',
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
if (violation.message.includes('local state variables')) {
|
|
830
|
+
return {
|
|
831
|
+
title: 'Extract to custom hook',
|
|
832
|
+
kind: 'refactor',
|
|
833
|
+
edit: { changes: {} },
|
|
834
|
+
isPreferred: true,
|
|
835
|
+
confidence: 0.7,
|
|
836
|
+
preview: 'Extract related state into a custom hook',
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Find all React components in a file
|
|
843
|
+
*/
|
|
844
|
+
findComponentsInFile(context) {
|
|
845
|
+
const components = [];
|
|
846
|
+
if (context.ast) {
|
|
847
|
+
// Use AST to find components
|
|
848
|
+
const functionNodes = this.findNodesByTypes(context.ast, [
|
|
849
|
+
'function_declaration',
|
|
850
|
+
'arrow_function',
|
|
851
|
+
'function_expression',
|
|
852
|
+
]);
|
|
853
|
+
for (const node of functionNodes) {
|
|
854
|
+
if (isReactComponent(node, context.content)) {
|
|
855
|
+
const info = analyzeComponentState(node, context.content, context.file, this.config);
|
|
856
|
+
if (info) {
|
|
857
|
+
components.push(info);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
// Fallback: use regex-based detection
|
|
864
|
+
const componentMatches = this.findComponentsWithRegex(context.content, context.file);
|
|
865
|
+
components.push(...componentMatches);
|
|
866
|
+
}
|
|
867
|
+
return components;
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Find components using regex (fallback when AST is not available)
|
|
871
|
+
*/
|
|
872
|
+
findComponentsWithRegex(content, filePath) {
|
|
873
|
+
const components = [];
|
|
874
|
+
// Match component patterns
|
|
875
|
+
const patterns = [
|
|
876
|
+
// Arrow function: const Button = ({ ... }) => ...
|
|
877
|
+
/(?:export\s+)?const\s+([A-Z][a-zA-Z0-9]*)\s*(?::\s*(?:React\.)?(?:FC|FunctionComponent)\s*<[^>]*>\s*)?=\s*\([^)]*\)\s*=>\s*\{[\s\S]*?\n\}/g,
|
|
878
|
+
// Function declaration: function Button({ ... }) { ... }
|
|
879
|
+
/(?:export\s+)?function\s+([A-Z][a-zA-Z0-9]*)\s*\([^)]*\)\s*\{[\s\S]*?\n\}/g,
|
|
880
|
+
];
|
|
881
|
+
for (const pattern of patterns) {
|
|
882
|
+
let match;
|
|
883
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
884
|
+
const componentName = match[1];
|
|
885
|
+
if (!componentName)
|
|
886
|
+
continue;
|
|
887
|
+
const sourceCode = match[0];
|
|
888
|
+
const beforeMatch = content.slice(0, match.index);
|
|
889
|
+
const line = beforeMatch.split('\n').length;
|
|
890
|
+
// Check if it returns JSX
|
|
891
|
+
if (!sourceCode.includes('<') || (!sourceCode.includes('/>') && !sourceCode.includes('</'))) {
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
const localState = detectLocalState(sourceCode);
|
|
895
|
+
const globalState = detectGlobalState(sourceCode, this.config);
|
|
896
|
+
const props = extractPropsFromComponent(sourceCode);
|
|
897
|
+
const passedDownProps = detectPropDrilling(sourceCode, props);
|
|
898
|
+
const issues = detectStateIssues(localState, globalState, passedDownProps, this.config);
|
|
899
|
+
const complexityScore = calculateComplexityScore(localState, globalState, issues);
|
|
900
|
+
components.push({
|
|
901
|
+
componentName,
|
|
902
|
+
filePath,
|
|
903
|
+
line,
|
|
904
|
+
column: 1,
|
|
905
|
+
localState,
|
|
906
|
+
globalState,
|
|
907
|
+
issues,
|
|
908
|
+
passedDownProps,
|
|
909
|
+
complexityScore,
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return components;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Create a pattern match for local state patterns
|
|
917
|
+
*/
|
|
918
|
+
createLocalStatePattern(file, analysis) {
|
|
919
|
+
return {
|
|
920
|
+
patternId: `state-local-${analysis.dominantLocalPattern}`,
|
|
921
|
+
location: { file, line: 1, column: 1 },
|
|
922
|
+
confidence: analysis.confidence.localPattern,
|
|
923
|
+
isOutlier: false,
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Create a pattern match for global state patterns
|
|
928
|
+
*/
|
|
929
|
+
createGlobalStatePattern(file, analysis) {
|
|
930
|
+
return {
|
|
931
|
+
patternId: `state-global-${analysis.dominantGlobalPattern}`,
|
|
932
|
+
location: { file, line: 1, column: 1 },
|
|
933
|
+
confidence: analysis.confidence.globalPattern,
|
|
934
|
+
isOutlier: false,
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Create a violation for a state issue
|
|
939
|
+
*/
|
|
940
|
+
createIssueViolation(file, component, issue) {
|
|
941
|
+
const range = {
|
|
942
|
+
start: { line: issue.line, character: issue.column },
|
|
943
|
+
end: { line: issue.line, character: issue.column + 1 },
|
|
944
|
+
};
|
|
945
|
+
return {
|
|
946
|
+
id: `state-${issue.type}-${component.componentName}-${file.replace(/[^a-zA-Z0-9]/g, '-')}`,
|
|
947
|
+
patternId: 'components/state-patterns',
|
|
948
|
+
severity: issue.severity,
|
|
949
|
+
file,
|
|
950
|
+
range,
|
|
951
|
+
message: `[${component.componentName}] ${issue.description}`,
|
|
952
|
+
expected: issue.suggestion,
|
|
953
|
+
actual: issue.description,
|
|
954
|
+
aiExplainAvailable: true,
|
|
955
|
+
aiFixAvailable: issue.type === 'prop-drilling' || issue.type === 'local-should-lift',
|
|
956
|
+
firstSeen: new Date(),
|
|
957
|
+
occurrences: 1,
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
// ============================================================================
|
|
962
|
+
// Factory Function
|
|
963
|
+
// ============================================================================
|
|
964
|
+
/**
|
|
965
|
+
* Create a new StatePatternDetector instance
|
|
966
|
+
*/
|
|
967
|
+
export function createStatePatternDetector(config) {
|
|
968
|
+
return new StatePatternDetector(config);
|
|
969
|
+
}
|
|
970
|
+
//# sourceMappingURL=state-patterns.js.map
|