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,1050 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response Envelope Detector - Response format pattern detection
|
|
3
|
+
*
|
|
4
|
+
* Detects response envelope patterns including:
|
|
5
|
+
* - Standard response envelope patterns ({ data, error, meta, success, message })
|
|
6
|
+
* - JSON:API format ({ data, errors, meta, links, included })
|
|
7
|
+
* - HAL format ({ _links, _embedded })
|
|
8
|
+
* - Pagination metadata ({ page, limit, total, hasMore, nextCursor })
|
|
9
|
+
* - Success/error response structures
|
|
10
|
+
* - Response.json() usage in Next.js
|
|
11
|
+
* - Express res.json() / res.send() patterns
|
|
12
|
+
*
|
|
13
|
+
* Flags violations:
|
|
14
|
+
* - Inconsistent response envelope structure across endpoints
|
|
15
|
+
* - Missing standard fields (data, error, success)
|
|
16
|
+
* - Mixing different response formats (JSON:API vs custom)
|
|
17
|
+
* - Raw data responses without envelope
|
|
18
|
+
* - Inconsistent pagination format
|
|
19
|
+
*
|
|
20
|
+
* @requirements 10.3 - THE API_Detector SHALL detect response envelope patterns ({ data, error, meta })
|
|
21
|
+
*/
|
|
22
|
+
import { RegexDetector } from '../base/index.js';
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Constants
|
|
25
|
+
// ============================================================================
|
|
26
|
+
/**
|
|
27
|
+
* Standard envelope fields
|
|
28
|
+
*/
|
|
29
|
+
export const STANDARD_ENVELOPE_FIELDS = {
|
|
30
|
+
data: ['data', 'result', 'results', 'payload', 'body'],
|
|
31
|
+
error: ['error', 'errors', 'err', 'errorMessage', 'message'],
|
|
32
|
+
meta: ['meta', 'metadata', '_meta', 'info'],
|
|
33
|
+
success: ['success', 'ok', 'status', 'isSuccess', 'succeeded'],
|
|
34
|
+
message: ['message', 'msg', 'statusMessage', 'description'],
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* JSON:API envelope fields
|
|
38
|
+
*/
|
|
39
|
+
export const JSON_API_FIELDS = ['data', 'errors', 'meta', 'links', 'included', 'jsonapi'];
|
|
40
|
+
/**
|
|
41
|
+
* HAL format fields
|
|
42
|
+
*/
|
|
43
|
+
export const HAL_FIELDS = ['_links', '_embedded'];
|
|
44
|
+
/**
|
|
45
|
+
* GraphQL response fields
|
|
46
|
+
*/
|
|
47
|
+
export const GRAPHQL_FIELDS = ['data', 'errors', 'extensions'];
|
|
48
|
+
/**
|
|
49
|
+
* Pagination fields by format
|
|
50
|
+
* Note: Fields are organized to avoid overlap where possible
|
|
51
|
+
* - offset: uses limit/offset terminology
|
|
52
|
+
* - pageBased: uses pageSize/totalPages terminology
|
|
53
|
+
* - cursor: uses cursor/hasMore terminology
|
|
54
|
+
* - linkBased: uses next/prev/first/last links
|
|
55
|
+
*/
|
|
56
|
+
export const PAGINATION_FIELDS = {
|
|
57
|
+
offset: ['limit', 'offset', 'total', 'totalCount', 'count'],
|
|
58
|
+
cursor: ['cursor', 'nextCursor', 'prevCursor', 'hasMore', 'hasNext', 'hasPrev', 'endCursor', 'startCursor'],
|
|
59
|
+
pageBased: ['pageSize', 'totalPages', 'currentPage', 'perPage'],
|
|
60
|
+
linkBased: ['next', 'prev', 'first', 'last', 'self'],
|
|
61
|
+
// Common fields that can appear in multiple formats
|
|
62
|
+
common: ['page'],
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Response patterns for Next.js
|
|
66
|
+
*/
|
|
67
|
+
export const NEXTJS_RESPONSE_PATTERNS = [
|
|
68
|
+
// Response.json({ data, error })
|
|
69
|
+
/Response\.json\s*\(\s*\{([^}]+)\}/gi,
|
|
70
|
+
// NextResponse.json({ data, error })
|
|
71
|
+
/NextResponse\.json\s*\(\s*\{([^}]+)\}/gi,
|
|
72
|
+
// return new Response(JSON.stringify({ data }))
|
|
73
|
+
/new\s+Response\s*\(\s*JSON\.stringify\s*\(\s*\{([^}]+)\}/gi,
|
|
74
|
+
];
|
|
75
|
+
/**
|
|
76
|
+
* Response patterns for Express
|
|
77
|
+
*/
|
|
78
|
+
export const EXPRESS_RESPONSE_PATTERNS = [
|
|
79
|
+
// res.json({ data, error })
|
|
80
|
+
/res\.json\s*\(\s*\{([^}]+)\}/gi,
|
|
81
|
+
// res.send({ data, error })
|
|
82
|
+
/res\.send\s*\(\s*\{([^}]+)\}/gi,
|
|
83
|
+
// res.status(200).json({ data })
|
|
84
|
+
/res\.status\s*\([^)]+\)\s*\.json\s*\(\s*\{([^}]+)\}/gi,
|
|
85
|
+
// res.status(200).send({ data })
|
|
86
|
+
/res\.status\s*\([^)]+\)\s*\.send\s*\(\s*\{([^}]+)\}/gi,
|
|
87
|
+
];
|
|
88
|
+
/**
|
|
89
|
+
* Generic response object patterns
|
|
90
|
+
*/
|
|
91
|
+
export const RESPONSE_OBJECT_PATTERNS = [
|
|
92
|
+
// return { data, error, success }
|
|
93
|
+
/return\s+\{([^}]+)\}/gi,
|
|
94
|
+
// const response = { data, error }
|
|
95
|
+
/(?:const|let|var)\s+(?:response|result|res)\s*=\s*\{([^}]+)\}/gi,
|
|
96
|
+
// { data: ..., error: ..., meta: ... }
|
|
97
|
+
/\{\s*(?:data|error|success|meta|message)\s*:/gi,
|
|
98
|
+
];
|
|
99
|
+
/**
|
|
100
|
+
* Error response patterns
|
|
101
|
+
*/
|
|
102
|
+
export const ERROR_RESPONSE_PATTERNS = [
|
|
103
|
+
// { error: { message, code } }
|
|
104
|
+
/\{\s*error\s*:\s*\{([^}]+)\}\s*\}/gi,
|
|
105
|
+
// { errors: [...] }
|
|
106
|
+
/\{\s*errors\s*:\s*\[/gi,
|
|
107
|
+
// { success: false, error: ... }
|
|
108
|
+
/\{\s*success\s*:\s*false\s*,\s*error/gi,
|
|
109
|
+
// { ok: false, message: ... }
|
|
110
|
+
/\{\s*ok\s*:\s*false\s*,\s*message/gi,
|
|
111
|
+
];
|
|
112
|
+
/**
|
|
113
|
+
* File patterns to exclude from detection
|
|
114
|
+
*/
|
|
115
|
+
export const EXCLUDED_FILE_PATTERNS = [
|
|
116
|
+
/\.test\.[jt]sx?$/,
|
|
117
|
+
/\.spec\.[jt]sx?$/,
|
|
118
|
+
/\.stories\.[jt]sx?$/,
|
|
119
|
+
/\.d\.ts$/,
|
|
120
|
+
/node_modules\//,
|
|
121
|
+
/\.mock\.[jt]sx?$/,
|
|
122
|
+
];
|
|
123
|
+
// ============================================================================
|
|
124
|
+
// Helper Functions
|
|
125
|
+
// ============================================================================
|
|
126
|
+
/**
|
|
127
|
+
* Check if a file should be excluded from detection
|
|
128
|
+
*/
|
|
129
|
+
export function shouldExcludeFile(filePath) {
|
|
130
|
+
return EXCLUDED_FILE_PATTERNS.some(pattern => pattern.test(filePath));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Check if position is inside a comment
|
|
134
|
+
*/
|
|
135
|
+
function isInsideComment(content, index) {
|
|
136
|
+
const beforeIndex = content.slice(0, index);
|
|
137
|
+
// Check for single-line comment
|
|
138
|
+
const lastNewline = beforeIndex.lastIndexOf('\n');
|
|
139
|
+
const currentLine = beforeIndex.slice(lastNewline + 1);
|
|
140
|
+
if (currentLine.includes('//')) {
|
|
141
|
+
const commentStart = currentLine.indexOf('//');
|
|
142
|
+
const positionInLine = index - lastNewline - 1;
|
|
143
|
+
if (positionInLine > commentStart) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Check for multi-line comment
|
|
148
|
+
const lastBlockCommentStart = beforeIndex.lastIndexOf('/*');
|
|
149
|
+
const lastBlockCommentEnd = beforeIndex.lastIndexOf('*/');
|
|
150
|
+
if (lastBlockCommentStart > lastBlockCommentEnd) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get line and column from index
|
|
157
|
+
*/
|
|
158
|
+
function getPositionFromIndex(content, index) {
|
|
159
|
+
const beforeMatch = content.slice(0, index);
|
|
160
|
+
const lineNumber = beforeMatch.split('\n').length;
|
|
161
|
+
const lastNewline = beforeMatch.lastIndexOf('\n');
|
|
162
|
+
const column = index - lastNewline;
|
|
163
|
+
return { line: lineNumber, column };
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Extract field names from an object literal string
|
|
167
|
+
*/
|
|
168
|
+
export function extractFieldNames(objectContent) {
|
|
169
|
+
const fields = [];
|
|
170
|
+
// Match field names in object literal: { field1: ..., field2: ... }
|
|
171
|
+
const fieldPattern = /([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g;
|
|
172
|
+
let match;
|
|
173
|
+
while ((match = fieldPattern.exec(objectContent)) !== null) {
|
|
174
|
+
if (match[1]) {
|
|
175
|
+
fields.push(match[1]);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return fields;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Detect the envelope format from fields
|
|
182
|
+
*/
|
|
183
|
+
export function detectEnvelopeFormat(fields) {
|
|
184
|
+
const lowerFields = fields.map(f => f.toLowerCase());
|
|
185
|
+
// Check for HAL format (most specific)
|
|
186
|
+
if (lowerFields.includes('_links') || lowerFields.includes('_embedded')) {
|
|
187
|
+
return 'hal';
|
|
188
|
+
}
|
|
189
|
+
// Check for JSON:API format
|
|
190
|
+
const jsonApiMatches = JSON_API_FIELDS.filter(f => lowerFields.includes(f.toLowerCase()));
|
|
191
|
+
if (jsonApiMatches.length >= 2 && lowerFields.includes('data')) {
|
|
192
|
+
// JSON:API typically has 'data' plus 'links', 'included', or 'jsonapi'
|
|
193
|
+
if (lowerFields.includes('links') || lowerFields.includes('included') || lowerFields.includes('jsonapi')) {
|
|
194
|
+
return 'json-api';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Check for GraphQL format
|
|
198
|
+
if (lowerFields.includes('data') && lowerFields.includes('extensions')) {
|
|
199
|
+
return 'graphql';
|
|
200
|
+
}
|
|
201
|
+
// Check for standard envelope format
|
|
202
|
+
const hasDataField = STANDARD_ENVELOPE_FIELDS.data.some(f => lowerFields.includes(f.toLowerCase()));
|
|
203
|
+
const hasErrorField = STANDARD_ENVELOPE_FIELDS.error.some(f => lowerFields.includes(f.toLowerCase()));
|
|
204
|
+
const hasSuccessField = STANDARD_ENVELOPE_FIELDS.success.some(f => lowerFields.includes(f.toLowerCase()));
|
|
205
|
+
const hasMetaField = STANDARD_ENVELOPE_FIELDS.meta.some(f => lowerFields.includes(f.toLowerCase()));
|
|
206
|
+
if (hasDataField || hasErrorField || hasSuccessField || hasMetaField) {
|
|
207
|
+
return 'standard';
|
|
208
|
+
}
|
|
209
|
+
// Check if it looks like a custom envelope (has some structure)
|
|
210
|
+
if (fields.length >= 2) {
|
|
211
|
+
return 'custom';
|
|
212
|
+
}
|
|
213
|
+
return 'direct';
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Detect pagination format from fields
|
|
217
|
+
*/
|
|
218
|
+
export function detectPaginationFormat(fields) {
|
|
219
|
+
const lowerFields = fields.map(f => f.toLowerCase());
|
|
220
|
+
let offsetMatches = 0;
|
|
221
|
+
let cursorMatches = 0;
|
|
222
|
+
let pageBasedMatches = 0;
|
|
223
|
+
let linkBasedMatches = 0;
|
|
224
|
+
let hasCommonPageField = false;
|
|
225
|
+
for (const field of lowerFields) {
|
|
226
|
+
// Check common fields
|
|
227
|
+
if (PAGINATION_FIELDS.common.some(f => f.toLowerCase() === field)) {
|
|
228
|
+
hasCommonPageField = true;
|
|
229
|
+
}
|
|
230
|
+
// Check specific format fields
|
|
231
|
+
if (PAGINATION_FIELDS.offset.some(f => f.toLowerCase() === field))
|
|
232
|
+
offsetMatches++;
|
|
233
|
+
if (PAGINATION_FIELDS.cursor.some(f => f.toLowerCase() === field))
|
|
234
|
+
cursorMatches++;
|
|
235
|
+
if (PAGINATION_FIELDS.pageBased.some(f => f.toLowerCase() === field))
|
|
236
|
+
pageBasedMatches++;
|
|
237
|
+
if (PAGINATION_FIELDS.linkBased.some(f => f.toLowerCase() === field))
|
|
238
|
+
linkBasedMatches++;
|
|
239
|
+
}
|
|
240
|
+
// Add common field to the most likely format
|
|
241
|
+
if (hasCommonPageField) {
|
|
242
|
+
// If we have limit/offset fields, it's offset pagination
|
|
243
|
+
if (offsetMatches > 0) {
|
|
244
|
+
offsetMatches++;
|
|
245
|
+
}
|
|
246
|
+
// If we have pageSize/totalPages, it's page-based
|
|
247
|
+
else if (pageBasedMatches > 0) {
|
|
248
|
+
pageBasedMatches++;
|
|
249
|
+
}
|
|
250
|
+
// Default to offset if only 'page' is present with total/count
|
|
251
|
+
else if (lowerFields.includes('total') || lowerFields.includes('count') || lowerFields.includes('limit')) {
|
|
252
|
+
offsetMatches++;
|
|
253
|
+
}
|
|
254
|
+
// Otherwise treat as page-based
|
|
255
|
+
else {
|
|
256
|
+
pageBasedMatches++;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Determine dominant format
|
|
260
|
+
const maxMatches = Math.max(offsetMatches, cursorMatches, pageBasedMatches, linkBasedMatches);
|
|
261
|
+
if (maxMatches === 0) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
// Check for mixed formats (multiple distinct format types with significant matches)
|
|
265
|
+
const significantFormats = [
|
|
266
|
+
offsetMatches > 0 ? 1 : 0,
|
|
267
|
+
cursorMatches > 0 ? 1 : 0,
|
|
268
|
+
pageBasedMatches > 0 ? 1 : 0,
|
|
269
|
+
linkBasedMatches > 0 ? 1 : 0,
|
|
270
|
+
].reduce((a, b) => a + b, 0);
|
|
271
|
+
if (significantFormats > 1 && cursorMatches > 0) {
|
|
272
|
+
// Mixing cursor with other formats is definitely mixed
|
|
273
|
+
return 'mixed';
|
|
274
|
+
}
|
|
275
|
+
if (cursorMatches === maxMatches)
|
|
276
|
+
return 'cursor';
|
|
277
|
+
if (linkBasedMatches === maxMatches)
|
|
278
|
+
return 'link-based';
|
|
279
|
+
if (pageBasedMatches === maxMatches && pageBasedMatches > offsetMatches)
|
|
280
|
+
return 'page-based';
|
|
281
|
+
if (offsetMatches > 0)
|
|
282
|
+
return 'offset';
|
|
283
|
+
if (pageBasedMatches > 0)
|
|
284
|
+
return 'page-based';
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Check if fields indicate a list/collection response
|
|
289
|
+
*/
|
|
290
|
+
export function isListResponse(fields, content) {
|
|
291
|
+
const lowerFields = fields.map(f => f.toLowerCase());
|
|
292
|
+
// Check for array indicators
|
|
293
|
+
if (lowerFields.includes('items') || lowerFields.includes('results') || lowerFields.includes('list')) {
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
// Check for pagination fields (any format)
|
|
297
|
+
const allPaginationFields = [
|
|
298
|
+
...PAGINATION_FIELDS.offset,
|
|
299
|
+
...PAGINATION_FIELDS.cursor,
|
|
300
|
+
...PAGINATION_FIELDS.pageBased,
|
|
301
|
+
...PAGINATION_FIELDS.common,
|
|
302
|
+
];
|
|
303
|
+
const hasPagination = allPaginationFields.some(f => lowerFields.includes(f.toLowerCase()));
|
|
304
|
+
if (hasPagination) {
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
// Check for array in data field
|
|
308
|
+
if (content.includes('data: [') || content.includes('data:[]') || content.includes('results: [')) {
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Detect Next.js response patterns
|
|
315
|
+
*/
|
|
316
|
+
export function detectNextjsResponses(content, file) {
|
|
317
|
+
const results = [];
|
|
318
|
+
const lines = content.split('\n');
|
|
319
|
+
for (const pattern of NEXTJS_RESPONSE_PATTERNS) {
|
|
320
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
321
|
+
let match;
|
|
322
|
+
while ((match = regex.exec(content)) !== null) {
|
|
323
|
+
if (isInsideComment(content, match.index)) {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const { line, column } = getPositionFromIndex(content, match.index);
|
|
327
|
+
const objectContent = match[1] || '';
|
|
328
|
+
const fields = extractFieldNames(objectContent);
|
|
329
|
+
const format = detectEnvelopeFormat(fields);
|
|
330
|
+
const paginationFormat = detectPaginationFormat(fields);
|
|
331
|
+
results.push({
|
|
332
|
+
type: 'nextjs-response',
|
|
333
|
+
format,
|
|
334
|
+
file,
|
|
335
|
+
line,
|
|
336
|
+
column,
|
|
337
|
+
matchedText: match[0],
|
|
338
|
+
fields,
|
|
339
|
+
paginationFormat: paginationFormat || undefined,
|
|
340
|
+
context: lines[line - 1] || '',
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return results;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Detect Express response patterns
|
|
348
|
+
*/
|
|
349
|
+
export function detectExpressResponses(content, file) {
|
|
350
|
+
const results = [];
|
|
351
|
+
const lines = content.split('\n');
|
|
352
|
+
for (const pattern of EXPRESS_RESPONSE_PATTERNS) {
|
|
353
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
354
|
+
let match;
|
|
355
|
+
while ((match = regex.exec(content)) !== null) {
|
|
356
|
+
if (isInsideComment(content, match.index)) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const { line, column } = getPositionFromIndex(content, match.index);
|
|
360
|
+
const objectContent = match[1] || '';
|
|
361
|
+
const fields = extractFieldNames(objectContent);
|
|
362
|
+
const format = detectEnvelopeFormat(fields);
|
|
363
|
+
const paginationFormat = detectPaginationFormat(fields);
|
|
364
|
+
results.push({
|
|
365
|
+
type: 'express-response',
|
|
366
|
+
format,
|
|
367
|
+
file,
|
|
368
|
+
line,
|
|
369
|
+
column,
|
|
370
|
+
matchedText: match[0],
|
|
371
|
+
fields,
|
|
372
|
+
paginationFormat: paginationFormat || undefined,
|
|
373
|
+
context: lines[line - 1] || '',
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return results;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Detect generic response object patterns
|
|
381
|
+
*/
|
|
382
|
+
export function detectResponseObjects(content, file) {
|
|
383
|
+
const results = [];
|
|
384
|
+
const lines = content.split('\n');
|
|
385
|
+
for (const pattern of RESPONSE_OBJECT_PATTERNS) {
|
|
386
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
387
|
+
let match;
|
|
388
|
+
while ((match = regex.exec(content)) !== null) {
|
|
389
|
+
if (isInsideComment(content, match.index)) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
const { line, column } = getPositionFromIndex(content, match.index);
|
|
393
|
+
const objectContent = match[1] || match[0];
|
|
394
|
+
const fields = extractFieldNames(objectContent);
|
|
395
|
+
// Skip if no relevant fields found
|
|
396
|
+
if (fields.length === 0) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
const format = detectEnvelopeFormat(fields);
|
|
400
|
+
// Skip direct format unless it has some envelope-like structure
|
|
401
|
+
if (format === 'direct' && fields.length < 2) {
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
const paginationFormat = detectPaginationFormat(fields);
|
|
405
|
+
results.push({
|
|
406
|
+
type: 'envelope-structure',
|
|
407
|
+
format,
|
|
408
|
+
file,
|
|
409
|
+
line,
|
|
410
|
+
column,
|
|
411
|
+
matchedText: match[0],
|
|
412
|
+
fields,
|
|
413
|
+
paginationFormat: paginationFormat || undefined,
|
|
414
|
+
context: lines[line - 1] || '',
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return results;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Detect error response patterns
|
|
422
|
+
*/
|
|
423
|
+
export function detectErrorResponses(content, file) {
|
|
424
|
+
const results = [];
|
|
425
|
+
const lines = content.split('\n');
|
|
426
|
+
for (const pattern of ERROR_RESPONSE_PATTERNS) {
|
|
427
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
428
|
+
let match;
|
|
429
|
+
while ((match = regex.exec(content)) !== null) {
|
|
430
|
+
if (isInsideComment(content, match.index)) {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
const { line, column } = getPositionFromIndex(content, match.index);
|
|
434
|
+
const objectContent = match[1] || match[0];
|
|
435
|
+
const fields = extractFieldNames(objectContent);
|
|
436
|
+
const format = detectEnvelopeFormat(['error', ...fields]);
|
|
437
|
+
results.push({
|
|
438
|
+
type: 'error-response',
|
|
439
|
+
format,
|
|
440
|
+
file,
|
|
441
|
+
line,
|
|
442
|
+
column,
|
|
443
|
+
matchedText: match[0],
|
|
444
|
+
fields: ['error', ...fields],
|
|
445
|
+
context: lines[line - 1] || '',
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return results;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Detect pagination metadata patterns
|
|
453
|
+
*/
|
|
454
|
+
export function detectPaginationPatterns(content, file) {
|
|
455
|
+
const results = [];
|
|
456
|
+
const lines = content.split('\n');
|
|
457
|
+
// Pattern for pagination objects - include all pagination-related fields
|
|
458
|
+
const paginationPattern = /\{\s*(?:page|limit|offset|cursor|total|hasMore|nextCursor|pageSize|totalPages|currentPage|perPage|count|totalCount)\s*:/gi;
|
|
459
|
+
let match;
|
|
460
|
+
while ((match = paginationPattern.exec(content)) !== null) {
|
|
461
|
+
if (isInsideComment(content, match.index)) {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
// Find the full object
|
|
465
|
+
const startIndex = match.index;
|
|
466
|
+
let braceCount = 0;
|
|
467
|
+
let endIndex = startIndex;
|
|
468
|
+
for (let i = startIndex; i < content.length; i++) {
|
|
469
|
+
if (content[i] === '{')
|
|
470
|
+
braceCount++;
|
|
471
|
+
if (content[i] === '}') {
|
|
472
|
+
braceCount--;
|
|
473
|
+
if (braceCount === 0) {
|
|
474
|
+
endIndex = i + 1;
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const objectContent = content.slice(startIndex, endIndex);
|
|
480
|
+
const fields = extractFieldNames(objectContent);
|
|
481
|
+
const paginationFormat = detectPaginationFormat(fields);
|
|
482
|
+
if (paginationFormat) {
|
|
483
|
+
const { line, column } = getPositionFromIndex(content, match.index);
|
|
484
|
+
results.push({
|
|
485
|
+
type: 'pagination-metadata',
|
|
486
|
+
format: 'standard',
|
|
487
|
+
file,
|
|
488
|
+
line,
|
|
489
|
+
column,
|
|
490
|
+
matchedText: objectContent.slice(0, 100), // Truncate for readability
|
|
491
|
+
fields,
|
|
492
|
+
paginationFormat,
|
|
493
|
+
context: lines[line - 1] || '',
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return results;
|
|
498
|
+
}
|
|
499
|
+
// ============================================================================
|
|
500
|
+
// Violation Detection Functions
|
|
501
|
+
// ============================================================================
|
|
502
|
+
/**
|
|
503
|
+
* Detect inconsistent envelope structure violations
|
|
504
|
+
*/
|
|
505
|
+
export function detectInconsistentEnvelopeViolations(patterns, file) {
|
|
506
|
+
const violations = [];
|
|
507
|
+
// Group patterns by format
|
|
508
|
+
const formatCounts = {
|
|
509
|
+
'standard': 0,
|
|
510
|
+
'json-api': 0,
|
|
511
|
+
'hal': 0,
|
|
512
|
+
'graphql': 0,
|
|
513
|
+
'custom': 0,
|
|
514
|
+
'direct': 0,
|
|
515
|
+
};
|
|
516
|
+
for (const pattern of patterns) {
|
|
517
|
+
formatCounts[pattern.format]++;
|
|
518
|
+
}
|
|
519
|
+
// Find dominant format (excluding direct)
|
|
520
|
+
let dominantFormat = null;
|
|
521
|
+
let maxCount = 0;
|
|
522
|
+
for (const [format, count] of Object.entries(formatCounts)) {
|
|
523
|
+
if (format !== 'direct' && count > maxCount) {
|
|
524
|
+
maxCount = count;
|
|
525
|
+
dominantFormat = format;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
// Flag patterns that don't match dominant format
|
|
529
|
+
if (dominantFormat && maxCount >= 2) {
|
|
530
|
+
for (const pattern of patterns) {
|
|
531
|
+
if (pattern.format !== dominantFormat && pattern.format !== 'direct') {
|
|
532
|
+
violations.push({
|
|
533
|
+
type: 'mixed-formats',
|
|
534
|
+
file,
|
|
535
|
+
line: pattern.line,
|
|
536
|
+
column: pattern.column,
|
|
537
|
+
endLine: pattern.line,
|
|
538
|
+
endColumn: pattern.column + pattern.matchedText.length,
|
|
539
|
+
value: pattern.format,
|
|
540
|
+
issue: `Response uses ${pattern.format} format but project predominantly uses ${dominantFormat}`,
|
|
541
|
+
suggestedFix: `Convert to ${dominantFormat} format for consistency`,
|
|
542
|
+
lineContent: pattern.context || '',
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return violations;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Detect missing standard fields violations
|
|
551
|
+
*/
|
|
552
|
+
export function detectMissingFieldViolations(patterns, file) {
|
|
553
|
+
const violations = [];
|
|
554
|
+
// Check standard format responses for missing fields
|
|
555
|
+
const standardPatterns = patterns.filter(p => p.format === 'standard');
|
|
556
|
+
// Determine which fields are commonly used
|
|
557
|
+
const fieldUsage = {};
|
|
558
|
+
for (const pattern of standardPatterns) {
|
|
559
|
+
for (const field of pattern.fields || []) {
|
|
560
|
+
const lowerField = field.toLowerCase();
|
|
561
|
+
fieldUsage[lowerField] = (fieldUsage[lowerField] || 0) + 1;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// Check for missing common fields
|
|
565
|
+
const hasDataField = standardPatterns.some(p => p.fields?.some(f => STANDARD_ENVELOPE_FIELDS.data.includes(f.toLowerCase())));
|
|
566
|
+
const hasErrorField = standardPatterns.some(p => p.fields?.some(f => STANDARD_ENVELOPE_FIELDS.error.includes(f.toLowerCase())));
|
|
567
|
+
// If most responses have data field, flag those without
|
|
568
|
+
if (hasDataField) {
|
|
569
|
+
for (const pattern of standardPatterns) {
|
|
570
|
+
const patternHasData = pattern.fields?.some(f => STANDARD_ENVELOPE_FIELDS.data.includes(f.toLowerCase()));
|
|
571
|
+
const patternHasError = pattern.fields?.some(f => STANDARD_ENVELOPE_FIELDS.error.includes(f.toLowerCase()));
|
|
572
|
+
// Skip error responses - they don't need data field
|
|
573
|
+
if (patternHasError && !patternHasData) {
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
if (!patternHasData && !patternHasError && pattern.type !== 'error-response') {
|
|
577
|
+
violations.push({
|
|
578
|
+
type: 'missing-data-field',
|
|
579
|
+
file,
|
|
580
|
+
line: pattern.line,
|
|
581
|
+
column: pattern.column,
|
|
582
|
+
endLine: pattern.line,
|
|
583
|
+
endColumn: pattern.column + pattern.matchedText.length,
|
|
584
|
+
value: pattern.matchedText,
|
|
585
|
+
issue: 'Response envelope is missing a data field',
|
|
586
|
+
suggestedFix: 'Add a "data" field to wrap the response payload',
|
|
587
|
+
lineContent: pattern.context || '',
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
// If most responses have error handling, flag those without
|
|
593
|
+
if (hasErrorField && standardPatterns.length >= 3) {
|
|
594
|
+
const patternsWithError = standardPatterns.filter(p => p.fields?.some(f => STANDARD_ENVELOPE_FIELDS.error.includes(f.toLowerCase())));
|
|
595
|
+
// Only flag if majority have error field
|
|
596
|
+
if (patternsWithError.length > standardPatterns.length / 2) {
|
|
597
|
+
for (const pattern of standardPatterns) {
|
|
598
|
+
const patternHasError = pattern.fields?.some(f => STANDARD_ENVELOPE_FIELDS.error.includes(f.toLowerCase()));
|
|
599
|
+
if (!patternHasError && pattern.type !== 'error-response') {
|
|
600
|
+
violations.push({
|
|
601
|
+
type: 'missing-error-field',
|
|
602
|
+
file,
|
|
603
|
+
line: pattern.line,
|
|
604
|
+
column: pattern.column,
|
|
605
|
+
endLine: pattern.line,
|
|
606
|
+
endColumn: pattern.column + pattern.matchedText.length,
|
|
607
|
+
value: pattern.matchedText,
|
|
608
|
+
issue: 'Response envelope is missing an error field',
|
|
609
|
+
suggestedFix: 'Add an "error" field for consistent error handling',
|
|
610
|
+
lineContent: pattern.context || '',
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return violations;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Detect raw data response violations
|
|
620
|
+
*/
|
|
621
|
+
export function detectRawDataViolations(patterns, content, file) {
|
|
622
|
+
const violations = [];
|
|
623
|
+
const lines = content.split('\n');
|
|
624
|
+
// Check if file uses envelope pattern
|
|
625
|
+
const envelopePatterns = patterns.filter(p => p.format !== 'direct');
|
|
626
|
+
if (envelopePatterns.length === 0) {
|
|
627
|
+
return violations; // No envelope pattern established
|
|
628
|
+
}
|
|
629
|
+
// Look for direct array returns or simple object returns
|
|
630
|
+
const directReturnPatterns = [
|
|
631
|
+
// return [...] - direct array return
|
|
632
|
+
/return\s+\[/g,
|
|
633
|
+
// res.json([...]) - direct array in response
|
|
634
|
+
/res\.json\s*\(\s*\[/g,
|
|
635
|
+
// Response.json([...]) - direct array in Next.js
|
|
636
|
+
/Response\.json\s*\(\s*\[/g,
|
|
637
|
+
// NextResponse.json([...])
|
|
638
|
+
/NextResponse\.json\s*\(\s*\[/g,
|
|
639
|
+
];
|
|
640
|
+
for (const pattern of directReturnPatterns) {
|
|
641
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
642
|
+
let match;
|
|
643
|
+
while ((match = regex.exec(content)) !== null) {
|
|
644
|
+
if (isInsideComment(content, match.index)) {
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
const { line, column } = getPositionFromIndex(content, match.index);
|
|
648
|
+
violations.push({
|
|
649
|
+
type: 'raw-data-response',
|
|
650
|
+
file,
|
|
651
|
+
line,
|
|
652
|
+
column,
|
|
653
|
+
endLine: line,
|
|
654
|
+
endColumn: column + match[0].length,
|
|
655
|
+
value: match[0],
|
|
656
|
+
issue: 'Direct array/data return without envelope wrapper',
|
|
657
|
+
suggestedFix: 'Wrap response in envelope: { data: [...], success: true }',
|
|
658
|
+
lineContent: lines[line - 1] || '',
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return violations;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Detect inconsistent pagination violations
|
|
666
|
+
*/
|
|
667
|
+
export function detectInconsistentPaginationViolations(patterns, file) {
|
|
668
|
+
const violations = [];
|
|
669
|
+
// Get patterns with pagination
|
|
670
|
+
const paginatedPatterns = patterns.filter(p => p.paginationFormat);
|
|
671
|
+
if (paginatedPatterns.length < 2) {
|
|
672
|
+
return violations; // Not enough patterns to detect inconsistency
|
|
673
|
+
}
|
|
674
|
+
// Count pagination formats
|
|
675
|
+
const formatCounts = {
|
|
676
|
+
'offset': 0,
|
|
677
|
+
'cursor': 0,
|
|
678
|
+
'page-based': 0,
|
|
679
|
+
'link-based': 0,
|
|
680
|
+
'mixed': 0,
|
|
681
|
+
};
|
|
682
|
+
for (const pattern of paginatedPatterns) {
|
|
683
|
+
if (pattern.paginationFormat) {
|
|
684
|
+
formatCounts[pattern.paginationFormat]++;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
// Find dominant format
|
|
688
|
+
let dominantFormat = null;
|
|
689
|
+
let maxCount = 0;
|
|
690
|
+
for (const [format, count] of Object.entries(formatCounts)) {
|
|
691
|
+
if (format !== 'mixed' && count > maxCount) {
|
|
692
|
+
maxCount = count;
|
|
693
|
+
dominantFormat = format;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
// Flag inconsistent pagination
|
|
697
|
+
if (dominantFormat && maxCount >= 2) {
|
|
698
|
+
for (const pattern of paginatedPatterns) {
|
|
699
|
+
if (pattern.paginationFormat &&
|
|
700
|
+
pattern.paginationFormat !== dominantFormat &&
|
|
701
|
+
pattern.paginationFormat !== 'mixed') {
|
|
702
|
+
violations.push({
|
|
703
|
+
type: 'inconsistent-pagination',
|
|
704
|
+
file,
|
|
705
|
+
line: pattern.line,
|
|
706
|
+
column: pattern.column,
|
|
707
|
+
endLine: pattern.line,
|
|
708
|
+
endColumn: pattern.column + pattern.matchedText.length,
|
|
709
|
+
value: pattern.paginationFormat,
|
|
710
|
+
issue: `Pagination uses ${pattern.paginationFormat} format but project uses ${dominantFormat}`,
|
|
711
|
+
suggestedFix: `Convert to ${dominantFormat} pagination format`,
|
|
712
|
+
lineContent: pattern.context || '',
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
// Flag mixed pagination within single response
|
|
718
|
+
for (const pattern of paginatedPatterns) {
|
|
719
|
+
if (pattern.paginationFormat === 'mixed') {
|
|
720
|
+
violations.push({
|
|
721
|
+
type: 'inconsistent-pagination',
|
|
722
|
+
file,
|
|
723
|
+
line: pattern.line,
|
|
724
|
+
column: pattern.column,
|
|
725
|
+
endLine: pattern.line,
|
|
726
|
+
endColumn: pattern.column + pattern.matchedText.length,
|
|
727
|
+
value: 'mixed',
|
|
728
|
+
issue: 'Response mixes different pagination formats',
|
|
729
|
+
suggestedFix: 'Use a single consistent pagination format',
|
|
730
|
+
lineContent: pattern.context || '',
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return violations;
|
|
735
|
+
}
|
|
736
|
+
// ============================================================================
|
|
737
|
+
// Main Analysis Function
|
|
738
|
+
// ============================================================================
|
|
739
|
+
/**
|
|
740
|
+
* Analyze response envelope patterns in a file
|
|
741
|
+
*/
|
|
742
|
+
export function analyzeResponseEnvelope(content, file) {
|
|
743
|
+
// Skip excluded files
|
|
744
|
+
if (shouldExcludeFile(file)) {
|
|
745
|
+
return {
|
|
746
|
+
envelopePatterns: [],
|
|
747
|
+
violations: [],
|
|
748
|
+
dominantFormat: null,
|
|
749
|
+
paginationFormat: null,
|
|
750
|
+
usesConsistentEnvelope: true,
|
|
751
|
+
patternAdherenceConfidence: 1.0,
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
// Detect all response patterns
|
|
755
|
+
const nextjsResponses = detectNextjsResponses(content, file);
|
|
756
|
+
const expressResponses = detectExpressResponses(content, file);
|
|
757
|
+
const responseObjects = detectResponseObjects(content, file);
|
|
758
|
+
const errorResponses = detectErrorResponses(content, file);
|
|
759
|
+
const paginationPatterns = detectPaginationPatterns(content, file);
|
|
760
|
+
// Combine all patterns (deduplicate by line)
|
|
761
|
+
const seenLines = new Set();
|
|
762
|
+
const envelopePatterns = [];
|
|
763
|
+
for (const pattern of [...nextjsResponses, ...expressResponses, ...responseObjects, ...errorResponses, ...paginationPatterns]) {
|
|
764
|
+
if (!seenLines.has(pattern.line)) {
|
|
765
|
+
seenLines.add(pattern.line);
|
|
766
|
+
envelopePatterns.push(pattern);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
// Determine dominant format
|
|
770
|
+
const formatCounts = {
|
|
771
|
+
'standard': 0,
|
|
772
|
+
'json-api': 0,
|
|
773
|
+
'hal': 0,
|
|
774
|
+
'graphql': 0,
|
|
775
|
+
'custom': 0,
|
|
776
|
+
'direct': 0,
|
|
777
|
+
};
|
|
778
|
+
for (const pattern of envelopePatterns) {
|
|
779
|
+
formatCounts[pattern.format]++;
|
|
780
|
+
}
|
|
781
|
+
let dominantFormat = null;
|
|
782
|
+
let maxCount = 0;
|
|
783
|
+
for (const [format, count] of Object.entries(formatCounts)) {
|
|
784
|
+
if (format !== 'direct' && count > maxCount) {
|
|
785
|
+
maxCount = count;
|
|
786
|
+
dominantFormat = format;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
// Determine pagination format
|
|
790
|
+
const paginationFormats = envelopePatterns
|
|
791
|
+
.filter(p => p.paginationFormat)
|
|
792
|
+
.map(p => p.paginationFormat);
|
|
793
|
+
let paginationFormat = null;
|
|
794
|
+
if (paginationFormats.length > 0) {
|
|
795
|
+
const paginationCounts = {
|
|
796
|
+
'offset': 0,
|
|
797
|
+
'cursor': 0,
|
|
798
|
+
'page-based': 0,
|
|
799
|
+
'link-based': 0,
|
|
800
|
+
'mixed': 0,
|
|
801
|
+
};
|
|
802
|
+
for (const format of paginationFormats) {
|
|
803
|
+
paginationCounts[format]++;
|
|
804
|
+
}
|
|
805
|
+
let maxPaginationCount = 0;
|
|
806
|
+
for (const [format, count] of Object.entries(paginationCounts)) {
|
|
807
|
+
if (count > maxPaginationCount) {
|
|
808
|
+
maxPaginationCount = count;
|
|
809
|
+
paginationFormat = format;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
// Detect violations
|
|
814
|
+
const inconsistentEnvelopeViolations = detectInconsistentEnvelopeViolations(envelopePatterns, file);
|
|
815
|
+
const missingFieldViolations = detectMissingFieldViolations(envelopePatterns, file);
|
|
816
|
+
const rawDataViolations = detectRawDataViolations(envelopePatterns, content, file);
|
|
817
|
+
const paginationViolations = detectInconsistentPaginationViolations(envelopePatterns, file);
|
|
818
|
+
const violations = [
|
|
819
|
+
...inconsistentEnvelopeViolations,
|
|
820
|
+
...missingFieldViolations,
|
|
821
|
+
...rawDataViolations,
|
|
822
|
+
...paginationViolations,
|
|
823
|
+
];
|
|
824
|
+
// Determine consistency
|
|
825
|
+
const usesConsistentEnvelope = violations.length === 0 && envelopePatterns.length > 0;
|
|
826
|
+
// Calculate confidence
|
|
827
|
+
let patternAdherenceConfidence = 1.0;
|
|
828
|
+
if (envelopePatterns.length > 0 && violations.length > 0) {
|
|
829
|
+
patternAdherenceConfidence = Math.max(0, 1 - (violations.length / envelopePatterns.length));
|
|
830
|
+
}
|
|
831
|
+
else if (envelopePatterns.length === 0) {
|
|
832
|
+
patternAdherenceConfidence = 0.5;
|
|
833
|
+
}
|
|
834
|
+
return {
|
|
835
|
+
envelopePatterns,
|
|
836
|
+
violations,
|
|
837
|
+
dominantFormat,
|
|
838
|
+
paginationFormat,
|
|
839
|
+
usesConsistentEnvelope,
|
|
840
|
+
patternAdherenceConfidence,
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
// ============================================================================
|
|
844
|
+
// Response Envelope Detector Class
|
|
845
|
+
// ============================================================================
|
|
846
|
+
/**
|
|
847
|
+
* Detector for response envelope patterns
|
|
848
|
+
*
|
|
849
|
+
* Identifies response envelope patterns and flags violations of consistency.
|
|
850
|
+
*
|
|
851
|
+
* @requirements 10.3 - THE API_Detector SHALL detect response envelope patterns ({ data, error, meta })
|
|
852
|
+
*/
|
|
853
|
+
export class ResponseEnvelopeDetector extends RegexDetector {
|
|
854
|
+
id = 'api/response-envelope';
|
|
855
|
+
category = 'api';
|
|
856
|
+
subcategory = 'response-envelope';
|
|
857
|
+
name = 'Response Envelope Detector';
|
|
858
|
+
description = 'Detects response envelope patterns and flags inconsistencies';
|
|
859
|
+
supportedLanguages = ['typescript', 'javascript', 'python'];
|
|
860
|
+
/**
|
|
861
|
+
* Detect response envelope patterns and violations
|
|
862
|
+
*/
|
|
863
|
+
async detect(context) {
|
|
864
|
+
const patterns = [];
|
|
865
|
+
const violations = [];
|
|
866
|
+
// Analyze the file
|
|
867
|
+
const analysis = analyzeResponseEnvelope(context.content, context.file);
|
|
868
|
+
// Create pattern matches for envelope formats
|
|
869
|
+
if (analysis.dominantFormat) {
|
|
870
|
+
patterns.push({
|
|
871
|
+
patternId: `${this.id}/format-${analysis.dominantFormat}`,
|
|
872
|
+
location: {
|
|
873
|
+
file: context.file,
|
|
874
|
+
line: analysis.envelopePatterns[0]?.line || 1,
|
|
875
|
+
column: analysis.envelopePatterns[0]?.column || 1,
|
|
876
|
+
},
|
|
877
|
+
confidence: 1.0,
|
|
878
|
+
isOutlier: false,
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
// Create pattern matches for pagination format
|
|
882
|
+
if (analysis.paginationFormat) {
|
|
883
|
+
const paginatedPattern = analysis.envelopePatterns.find(p => p.paginationFormat);
|
|
884
|
+
patterns.push({
|
|
885
|
+
patternId: `${this.id}/pagination-${analysis.paginationFormat}`,
|
|
886
|
+
location: {
|
|
887
|
+
file: context.file,
|
|
888
|
+
line: paginatedPattern?.line || 1,
|
|
889
|
+
column: paginatedPattern?.column || 1,
|
|
890
|
+
},
|
|
891
|
+
confidence: 1.0,
|
|
892
|
+
isOutlier: false,
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
// Create pattern matches by type
|
|
896
|
+
const patternTypes = new Set(analysis.envelopePatterns.map(p => p.type));
|
|
897
|
+
for (const patternType of patternTypes) {
|
|
898
|
+
const firstOfType = analysis.envelopePatterns.find(p => p.type === patternType);
|
|
899
|
+
if (firstOfType) {
|
|
900
|
+
patterns.push({
|
|
901
|
+
patternId: `${this.id}/${patternType}`,
|
|
902
|
+
location: {
|
|
903
|
+
file: context.file,
|
|
904
|
+
line: firstOfType.line,
|
|
905
|
+
column: firstOfType.column,
|
|
906
|
+
},
|
|
907
|
+
confidence: 1.0,
|
|
908
|
+
isOutlier: false,
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
// Create violations
|
|
913
|
+
for (const violation of analysis.violations) {
|
|
914
|
+
violations.push(this.createViolation(violation));
|
|
915
|
+
}
|
|
916
|
+
return this.createResult(patterns, violations, analysis.patternAdherenceConfidence);
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Create a Violation from ResponseEnvelopeViolationInfo
|
|
920
|
+
*/
|
|
921
|
+
createViolation(info) {
|
|
922
|
+
const severityMap = {
|
|
923
|
+
'inconsistent-envelope': 'warning',
|
|
924
|
+
'missing-data-field': 'info',
|
|
925
|
+
'missing-error-field': 'info',
|
|
926
|
+
'missing-success-indicator': 'info',
|
|
927
|
+
'mixed-formats': 'warning',
|
|
928
|
+
'raw-data-response': 'info',
|
|
929
|
+
'inconsistent-pagination': 'warning',
|
|
930
|
+
'missing-pagination': 'info',
|
|
931
|
+
};
|
|
932
|
+
const violation = {
|
|
933
|
+
id: `${this.id}-${info.file}-${info.line}-${info.column}`,
|
|
934
|
+
patternId: this.id,
|
|
935
|
+
severity: severityMap[info.type] || 'warning',
|
|
936
|
+
file: info.file,
|
|
937
|
+
range: {
|
|
938
|
+
start: { line: info.line - 1, character: info.column - 1 },
|
|
939
|
+
end: { line: info.endLine - 1, character: info.endColumn - 1 },
|
|
940
|
+
},
|
|
941
|
+
message: info.issue,
|
|
942
|
+
explanation: this.getExplanation(info.type),
|
|
943
|
+
expected: info.suggestedFix || 'Consistent response envelope',
|
|
944
|
+
actual: info.value,
|
|
945
|
+
aiExplainAvailable: true,
|
|
946
|
+
aiFixAvailable: !!info.suggestedFix,
|
|
947
|
+
firstSeen: new Date(),
|
|
948
|
+
occurrences: 1,
|
|
949
|
+
};
|
|
950
|
+
if (info.suggestedFix) {
|
|
951
|
+
const quickFix = this.createQuickFixForViolation(info);
|
|
952
|
+
if (quickFix) {
|
|
953
|
+
violation.quickFix = quickFix;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return violation;
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Get explanation for a violation type
|
|
960
|
+
*/
|
|
961
|
+
getExplanation(type) {
|
|
962
|
+
const explanations = {
|
|
963
|
+
'inconsistent-envelope': 'Consistent response envelope structure makes APIs predictable and easier to consume. ' +
|
|
964
|
+
'Clients can rely on a standard structure for parsing responses.',
|
|
965
|
+
'missing-data-field': 'A "data" field in the response envelope clearly separates the payload from metadata. ' +
|
|
966
|
+
'This makes it easier to add metadata without breaking existing clients.',
|
|
967
|
+
'missing-error-field': 'An "error" field provides a consistent location for error information. ' +
|
|
968
|
+
'This helps clients handle errors uniformly across all endpoints.',
|
|
969
|
+
'missing-success-indicator': 'A success indicator (like "success" or "ok") makes it easy to check response status ' +
|
|
970
|
+
'without parsing the entire response or relying solely on HTTP status codes.',
|
|
971
|
+
'mixed-formats': 'Mixing different response formats (e.g., JSON:API with custom format) creates ' +
|
|
972
|
+
'inconsistency and makes the API harder to consume. Choose one format and use it consistently.',
|
|
973
|
+
'raw-data-response': 'Returning raw data without an envelope makes it harder to add metadata later. ' +
|
|
974
|
+
'Wrapping responses in an envelope provides flexibility for future changes.',
|
|
975
|
+
'inconsistent-pagination': 'Consistent pagination format across endpoints makes it easier for clients to ' +
|
|
976
|
+
'implement pagination logic. Use the same pagination style throughout the API.',
|
|
977
|
+
'missing-pagination': 'List endpoints should include pagination metadata to help clients navigate large datasets. ' +
|
|
978
|
+
'Include fields like total, page, limit, or cursor information.',
|
|
979
|
+
};
|
|
980
|
+
return explanations[type] || 'Follow consistent response envelope patterns.';
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Create a quick fix for a violation
|
|
984
|
+
*/
|
|
985
|
+
createQuickFixForViolation(info) {
|
|
986
|
+
if (!info.suggestedFix) {
|
|
987
|
+
return undefined;
|
|
988
|
+
}
|
|
989
|
+
return {
|
|
990
|
+
title: info.suggestedFix,
|
|
991
|
+
kind: 'quickfix',
|
|
992
|
+
edit: {
|
|
993
|
+
changes: {
|
|
994
|
+
[info.file]: [
|
|
995
|
+
{
|
|
996
|
+
range: {
|
|
997
|
+
start: { line: info.line - 1, character: 0 },
|
|
998
|
+
end: { line: info.line - 1, character: info.lineContent.length },
|
|
999
|
+
},
|
|
1000
|
+
newText: info.lineContent, // Placeholder - actual fix would need more context
|
|
1001
|
+
},
|
|
1002
|
+
],
|
|
1003
|
+
},
|
|
1004
|
+
},
|
|
1005
|
+
isPreferred: true,
|
|
1006
|
+
confidence: 0.5,
|
|
1007
|
+
preview: info.suggestedFix,
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Generate a quick fix for a violation
|
|
1012
|
+
*/
|
|
1013
|
+
generateQuickFix(violation) {
|
|
1014
|
+
// Check if this is a response envelope violation
|
|
1015
|
+
if (!violation.patternId.startsWith('api/response-envelope')) {
|
|
1016
|
+
return null;
|
|
1017
|
+
}
|
|
1018
|
+
// Generate appropriate fix based on violation type
|
|
1019
|
+
if (violation.message.includes('missing a data field')) {
|
|
1020
|
+
return {
|
|
1021
|
+
title: 'Wrap response in data field',
|
|
1022
|
+
kind: 'quickfix',
|
|
1023
|
+
edit: {
|
|
1024
|
+
changes: {
|
|
1025
|
+
[violation.file]: [
|
|
1026
|
+
{
|
|
1027
|
+
range: violation.range,
|
|
1028
|
+
newText: '{ data: ' + violation.actual + ' }',
|
|
1029
|
+
},
|
|
1030
|
+
],
|
|
1031
|
+
},
|
|
1032
|
+
},
|
|
1033
|
+
isPreferred: true,
|
|
1034
|
+
confidence: 0.6,
|
|
1035
|
+
preview: 'Wrap response payload in { data: ... }',
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
return null;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
// ============================================================================
|
|
1042
|
+
// Factory Function
|
|
1043
|
+
// ============================================================================
|
|
1044
|
+
/**
|
|
1045
|
+
* Create a new ResponseEnvelopeDetector instance
|
|
1046
|
+
*/
|
|
1047
|
+
export function createResponseEnvelopeDetector() {
|
|
1048
|
+
return new ResponseEnvelopeDetector();
|
|
1049
|
+
}
|
|
1050
|
+
//# sourceMappingURL=response-envelope.js.map
|