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,1181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailwind Patterns Detector - Tailwind CSS pattern consistency detection
|
|
3
|
+
*
|
|
4
|
+
* Detects Tailwind CSS patterns including:
|
|
5
|
+
* - Standard Tailwind utility classes
|
|
6
|
+
* - Tailwind arbitrary values (e.g., p-[13px], text-[#ff0000])
|
|
7
|
+
* - Tailwind responsive prefixes (sm:, md:, lg:, xl:, 2xl:)
|
|
8
|
+
* - Tailwind state variants (hover:, focus:, active:, disabled:)
|
|
9
|
+
* - Tailwind dark mode (dark:)
|
|
10
|
+
* - @apply directive usage
|
|
11
|
+
* - Tailwind config customizations
|
|
12
|
+
*
|
|
13
|
+
* Flags inconsistent Tailwind usage:
|
|
14
|
+
* - Mixing arbitrary values with standard classes
|
|
15
|
+
* - Inconsistent responsive breakpoint ordering
|
|
16
|
+
* - Redundant/conflicting classes (e.g., flex and block on same element)
|
|
17
|
+
* - Non-standard class ordering
|
|
18
|
+
*
|
|
19
|
+
* @requirements 9.6 - THE Styling_Detector SHALL detect Tailwind pattern consistency
|
|
20
|
+
*/
|
|
21
|
+
import { RegexDetector } from '../base/index.js';
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Constants
|
|
24
|
+
// ============================================================================
|
|
25
|
+
/**
|
|
26
|
+
* Standard Tailwind utility class patterns
|
|
27
|
+
*/
|
|
28
|
+
export const TAILWIND_UTILITY_PATTERNS = [
|
|
29
|
+
// Layout
|
|
30
|
+
/\b(?:flex|grid|block|inline|inline-block|inline-flex|inline-grid|hidden|contents|flow-root)\b/g,
|
|
31
|
+
/\b(?:flex-row|flex-col|flex-row-reverse|flex-col-reverse|flex-wrap|flex-nowrap|flex-wrap-reverse)\b/g,
|
|
32
|
+
/\b(?:flex-1|flex-auto|flex-initial|flex-none)\b/g,
|
|
33
|
+
/\b(?:grid-cols-\d+|grid-rows-\d+|col-span-\d+|row-span-\d+|col-start-\d+|col-end-\d+|row-start-\d+|row-end-\d+)\b/g,
|
|
34
|
+
// Spacing
|
|
35
|
+
/\b(?:p|px|py|pt|pr|pb|pl)-(?:\d+(?:\.\d+)?|auto|px)\b/g,
|
|
36
|
+
/\b(?:m|mx|my|mt|mr|mb|ml)-(?:\d+(?:\.\d+)?|auto|px)\b/g,
|
|
37
|
+
/\b(?:space-x|space-y)-(?:\d+(?:\.\d+)?|reverse)\b/g,
|
|
38
|
+
/\b(?:gap|gap-x|gap-y)-\d+(?:\.\d+)?\b/g,
|
|
39
|
+
// Sizing
|
|
40
|
+
/\b(?:w|h|min-w|min-h|max-w|max-h|size)-(?:\d+(?:\/\d+)?|full|screen|auto|min|max|fit|px)\b/g,
|
|
41
|
+
// Typography
|
|
42
|
+
/\b(?:text-(?:xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl))\b/g,
|
|
43
|
+
/\b(?:font-(?:thin|extralight|light|normal|medium|semibold|bold|extrabold|black))\b/g,
|
|
44
|
+
/\b(?:leading-(?:none|tight|snug|normal|relaxed|loose|\d+))\b/g,
|
|
45
|
+
/\b(?:tracking-(?:tighter|tight|normal|wide|wider|widest))\b/g,
|
|
46
|
+
// Colors
|
|
47
|
+
/\b(?:text|bg|border|ring|divide|outline|accent|caret|fill|stroke)-(?:inherit|current|transparent|black|white)\b/g,
|
|
48
|
+
/\b(?:text|bg|border|ring|divide|outline|accent|caret|fill|stroke)-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:\d{2,3}|50)\b/g,
|
|
49
|
+
// Flexbox alignment
|
|
50
|
+
/\b(?:items|justify|content|self|place-content|place-items|place-self)-(?:start|end|center|between|around|evenly|stretch|baseline|auto)\b/g,
|
|
51
|
+
// Border
|
|
52
|
+
/\b(?:border|border-t|border-r|border-b|border-l|border-x|border-y)(?:-\d+)?\b/g,
|
|
53
|
+
/\b(?:rounded|rounded-t|rounded-r|rounded-b|rounded-l|rounded-tl|rounded-tr|rounded-bl|rounded-br|rounded-s|rounded-e|rounded-ss|rounded-se|rounded-es|rounded-ee)(?:-(?:none|sm|md|lg|xl|2xl|3xl|full))?\b/g,
|
|
54
|
+
// Effects
|
|
55
|
+
/\b(?:shadow|shadow-sm|shadow-md|shadow-lg|shadow-xl|shadow-2xl|shadow-inner|shadow-none)\b/g,
|
|
56
|
+
/\b(?:opacity-\d+)\b/g,
|
|
57
|
+
// Positioning
|
|
58
|
+
/\b(?:relative|absolute|fixed|sticky|static)\b/g,
|
|
59
|
+
/\b(?:top|right|bottom|left|inset|inset-x|inset-y)-(?:\d+(?:\/\d+)?|auto|px|full)\b/g,
|
|
60
|
+
/\b(?:z-\d+|z-auto)\b/g,
|
|
61
|
+
// Display
|
|
62
|
+
/\b(?:overflow|overflow-x|overflow-y)-(?:auto|hidden|visible|scroll|clip)\b/g,
|
|
63
|
+
// Transitions
|
|
64
|
+
/\b(?:transition|transition-all|transition-colors|transition-opacity|transition-shadow|transition-transform|transition-none)\b/g,
|
|
65
|
+
/\b(?:duration-\d+)\b/g,
|
|
66
|
+
/\b(?:ease-linear|ease-in|ease-out|ease-in-out)\b/g,
|
|
67
|
+
/\b(?:delay-\d+)\b/g,
|
|
68
|
+
// Transforms
|
|
69
|
+
/\b(?:scale|scale-x|scale-y)-\d+\b/g,
|
|
70
|
+
/\b(?:rotate-\d+)\b/g,
|
|
71
|
+
/\b(?:translate-x|translate-y)-(?:\d+(?:\/\d+)?|full|px)\b/g,
|
|
72
|
+
/\b(?:skew-x|skew-y)-\d+\b/g,
|
|
73
|
+
// Cursor
|
|
74
|
+
/\b(?:cursor-(?:auto|default|pointer|wait|text|move|help|not-allowed|none|context-menu|progress|cell|crosshair|vertical-text|alias|copy|no-drop|grab|grabbing|all-scroll|col-resize|row-resize|n-resize|e-resize|s-resize|w-resize|ne-resize|nw-resize|se-resize|sw-resize|ew-resize|ns-resize|nesw-resize|nwse-resize|zoom-in|zoom-out))\b/g,
|
|
75
|
+
// Pointer events
|
|
76
|
+
/\b(?:pointer-events-(?:none|auto))\b/g,
|
|
77
|
+
// User select
|
|
78
|
+
/\b(?:select-(?:none|text|all|auto))\b/g,
|
|
79
|
+
// Visibility
|
|
80
|
+
/\b(?:visible|invisible|collapse)\b/g,
|
|
81
|
+
// Whitespace
|
|
82
|
+
/\b(?:whitespace-(?:normal|nowrap|pre|pre-line|pre-wrap|break-spaces))\b/g,
|
|
83
|
+
// Word break
|
|
84
|
+
/\b(?:break-(?:normal|words|all|keep))\b/g,
|
|
85
|
+
// Text alignment
|
|
86
|
+
/\b(?:text-(?:left|center|right|justify|start|end))\b/g,
|
|
87
|
+
// Text decoration
|
|
88
|
+
/\b(?:underline|overline|line-through|no-underline)\b/g,
|
|
89
|
+
// Text transform
|
|
90
|
+
/\b(?:uppercase|lowercase|capitalize|normal-case)\b/g,
|
|
91
|
+
// Aspect ratio
|
|
92
|
+
/\b(?:aspect-(?:auto|square|video))\b/g,
|
|
93
|
+
// Object fit
|
|
94
|
+
/\b(?:object-(?:contain|cover|fill|none|scale-down))\b/g,
|
|
95
|
+
];
|
|
96
|
+
/**
|
|
97
|
+
* Tailwind arbitrary value patterns
|
|
98
|
+
* Matches: p-[13px], text-[#ff0000], w-[calc(100%-20px)], etc.
|
|
99
|
+
*/
|
|
100
|
+
export const TAILWIND_ARBITRARY_VALUE_PATTERN = /\b([a-z][a-z0-9-]*)-\[([^\]]+)\]/gi;
|
|
101
|
+
/**
|
|
102
|
+
* Tailwind responsive prefix patterns
|
|
103
|
+
* Matches: sm:, md:, lg:, xl:, 2xl:
|
|
104
|
+
*/
|
|
105
|
+
export const TAILWIND_RESPONSIVE_PREFIXES = ['sm', 'md', 'lg', 'xl', '2xl'];
|
|
106
|
+
export const TAILWIND_RESPONSIVE_PATTERN = /\b(sm|md|lg|xl|2xl):([a-z][a-z0-9-]*(?:-[a-z0-9]+)*(?:\[[^\]]+\])?)/gi;
|
|
107
|
+
/**
|
|
108
|
+
* Tailwind state variant patterns
|
|
109
|
+
* Matches: hover:, focus:, active:, disabled:, etc.
|
|
110
|
+
*/
|
|
111
|
+
export const TAILWIND_STATE_VARIANTS = [
|
|
112
|
+
'hover', 'focus', 'active', 'disabled', 'visited', 'focus-within', 'focus-visible',
|
|
113
|
+
'first', 'last', 'odd', 'even', 'first-of-type', 'last-of-type', 'only-child', 'only-of-type',
|
|
114
|
+
'empty', 'enabled', 'checked', 'indeterminate', 'default', 'required', 'valid', 'invalid',
|
|
115
|
+
'in-range', 'out-of-range', 'placeholder-shown', 'autofill', 'read-only',
|
|
116
|
+
'before', 'after', 'first-letter', 'first-line', 'marker', 'selection', 'file', 'backdrop', 'placeholder',
|
|
117
|
+
'group-hover', 'group-focus', 'peer-hover', 'peer-focus', 'peer-checked',
|
|
118
|
+
];
|
|
119
|
+
export const TAILWIND_STATE_VARIANT_PATTERN = new RegExp(`\\b(${TAILWIND_STATE_VARIANTS.join('|')}):([a-z][a-z0-9-]*(?:-[a-z0-9]+)*(?:\\[[^\\]]+\\])?)`, 'gi');
|
|
120
|
+
/**
|
|
121
|
+
* Tailwind dark mode pattern
|
|
122
|
+
* Matches: dark:bg-gray-800, dark:text-white, etc.
|
|
123
|
+
*/
|
|
124
|
+
export const TAILWIND_DARK_MODE_PATTERN = /\bdark:([a-z][a-z0-9-]*(?:-[a-z0-9]+)*(?:\[[^\]]+\])?)/gi;
|
|
125
|
+
/**
|
|
126
|
+
* @apply directive pattern
|
|
127
|
+
* Matches: @apply flex items-center p-4;
|
|
128
|
+
*/
|
|
129
|
+
export const TAILWIND_APPLY_PATTERN = /@apply\s+([^;]+);?/gi;
|
|
130
|
+
/**
|
|
131
|
+
* Tailwind config customization patterns
|
|
132
|
+
*/
|
|
133
|
+
export const TAILWIND_CONFIG_PATTERNS = [
|
|
134
|
+
/theme:\s*\{/g,
|
|
135
|
+
/extend:\s*\{/g,
|
|
136
|
+
/plugins:\s*\[/g,
|
|
137
|
+
/content:\s*\[/g,
|
|
138
|
+
];
|
|
139
|
+
/**
|
|
140
|
+
* Conflicting class pairs - classes that shouldn't be used together
|
|
141
|
+
*/
|
|
142
|
+
export const CONFLICTING_CLASS_PAIRS = [
|
|
143
|
+
[/\bflex\b/, /\bgrid\b/, 'flex and grid'],
|
|
144
|
+
[/\bflex\b/, /\bblock\b/, 'flex and block'],
|
|
145
|
+
[/\bgrid\b/, /\bblock\b/, 'grid and block'],
|
|
146
|
+
[/\bhidden\b/, /\bflex\b/, 'hidden and flex'],
|
|
147
|
+
[/\bhidden\b/, /\bgrid\b/, 'hidden and grid'],
|
|
148
|
+
[/\bhidden\b/, /\bblock\b/, 'hidden and block'],
|
|
149
|
+
[/\bstatic\b/, /\brelative\b/, 'static and relative'],
|
|
150
|
+
[/\bstatic\b/, /\babsolute\b/, 'static and absolute'],
|
|
151
|
+
[/\bstatic\b/, /\bfixed\b/, 'static and fixed'],
|
|
152
|
+
[/\bstatic\b/, /\bsticky\b/, 'static and sticky'],
|
|
153
|
+
[/\brelative\b/, /\babsolute\b/, 'relative and absolute'],
|
|
154
|
+
[/\brelative\b/, /\bfixed\b/, 'relative and fixed'],
|
|
155
|
+
[/\babsolute\b/, /\bfixed\b/, 'absolute and fixed'],
|
|
156
|
+
[/\bvisible\b/, /\binvisible\b/, 'visible and invisible'],
|
|
157
|
+
[/\bunderline\b/, /\bno-underline\b/, 'underline and no-underline'],
|
|
158
|
+
[/\buppercase\b/, /\blowercase\b/, 'uppercase and lowercase'],
|
|
159
|
+
[/\buppercase\b/, /\bcapitalize\b/, 'uppercase and capitalize'],
|
|
160
|
+
[/\blowercase\b/, /\bcapitalize\b/, 'lowercase and capitalize'],
|
|
161
|
+
];
|
|
162
|
+
/**
|
|
163
|
+
* Recommended responsive breakpoint order
|
|
164
|
+
*/
|
|
165
|
+
export const RESPONSIVE_BREAKPOINT_ORDER = ['sm', 'md', 'lg', 'xl', '2xl'];
|
|
166
|
+
/**
|
|
167
|
+
* Recommended class ordering categories
|
|
168
|
+
*/
|
|
169
|
+
export const CLASS_ORDER_CATEGORIES = [
|
|
170
|
+
'layout', // flex, grid, block, hidden
|
|
171
|
+
'position', // relative, absolute, fixed, sticky
|
|
172
|
+
'display', // overflow, z-index
|
|
173
|
+
'sizing', // w-*, h-*, min-*, max-*
|
|
174
|
+
'spacing', // p-*, m-*, gap-*
|
|
175
|
+
'border', // border-*, rounded-*
|
|
176
|
+
'background', // bg-*
|
|
177
|
+
'typography', // text-*, font-*, leading-*
|
|
178
|
+
'effects', // shadow-*, opacity-*
|
|
179
|
+
'transitions', // transition-*, duration-*, ease-*
|
|
180
|
+
'transforms', // scale-*, rotate-*, translate-*
|
|
181
|
+
];
|
|
182
|
+
/**
|
|
183
|
+
* File patterns to exclude from Tailwind detection
|
|
184
|
+
*/
|
|
185
|
+
export const EXCLUDED_FILE_PATTERNS = [
|
|
186
|
+
/\.test\.[jt]sx?$/,
|
|
187
|
+
/\.spec\.[jt]sx?$/,
|
|
188
|
+
/\.stories\.[jt]sx?$/,
|
|
189
|
+
/tailwind\.config\.[jt]s$/,
|
|
190
|
+
/postcss\.config\.[jt]s$/,
|
|
191
|
+
];
|
|
192
|
+
// ============================================================================
|
|
193
|
+
// Helper Functions
|
|
194
|
+
// ============================================================================
|
|
195
|
+
/**
|
|
196
|
+
* Check if a file should be excluded from Tailwind detection
|
|
197
|
+
*/
|
|
198
|
+
export function shouldExcludeFile(filePath) {
|
|
199
|
+
return EXCLUDED_FILE_PATTERNS.some(pattern => pattern.test(filePath));
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Check if a position is inside a comment
|
|
203
|
+
*/
|
|
204
|
+
function isInsideComment(content, index) {
|
|
205
|
+
const beforeIndex = content.slice(0, index);
|
|
206
|
+
// Check for single-line comment
|
|
207
|
+
const lastNewline = beforeIndex.lastIndexOf('\n');
|
|
208
|
+
const currentLine = beforeIndex.slice(lastNewline + 1);
|
|
209
|
+
if (currentLine.includes('//')) {
|
|
210
|
+
const commentStart = currentLine.indexOf('//');
|
|
211
|
+
const positionInLine = index - lastNewline - 1;
|
|
212
|
+
if (positionInLine > commentStart) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Check for multi-line comment
|
|
217
|
+
const lastBlockCommentStart = beforeIndex.lastIndexOf('/*');
|
|
218
|
+
const lastBlockCommentEnd = beforeIndex.lastIndexOf('*/');
|
|
219
|
+
if (lastBlockCommentStart > lastBlockCommentEnd) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Check if a class is a standard Tailwind utility class
|
|
226
|
+
*/
|
|
227
|
+
export function isTailwindUtilityClass(className) {
|
|
228
|
+
for (const pattern of TAILWIND_UTILITY_PATTERNS) {
|
|
229
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
230
|
+
if (regex.test(className)) {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Check if a class uses arbitrary values
|
|
238
|
+
*/
|
|
239
|
+
export function isArbitraryValueClass(className) {
|
|
240
|
+
// Create new regex to avoid lastIndex issues with global flag
|
|
241
|
+
const regex = new RegExp(TAILWIND_ARBITRARY_VALUE_PATTERN.source, 'i');
|
|
242
|
+
return regex.test(className);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Check if a class has a responsive prefix
|
|
246
|
+
*/
|
|
247
|
+
export function hasResponsivePrefix(className) {
|
|
248
|
+
// Create new regex to avoid lastIndex issues with global flag
|
|
249
|
+
const regex = new RegExp(TAILWIND_RESPONSIVE_PATTERN.source, 'i');
|
|
250
|
+
return regex.test(className);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Check if a class has a state variant
|
|
254
|
+
*/
|
|
255
|
+
export function hasStateVariant(className) {
|
|
256
|
+
// Create new regex to avoid lastIndex issues with global flag
|
|
257
|
+
const regex = new RegExp(TAILWIND_STATE_VARIANT_PATTERN.source, 'i');
|
|
258
|
+
return regex.test(className);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Check if a class has dark mode prefix
|
|
262
|
+
*/
|
|
263
|
+
export function hasDarkModePrefix(className) {
|
|
264
|
+
// Create new regex to avoid lastIndex issues with global flag
|
|
265
|
+
const regex = new RegExp(TAILWIND_DARK_MODE_PATTERN.source, 'i');
|
|
266
|
+
return regex.test(className);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Extract the base class from a prefixed class
|
|
270
|
+
* e.g., "sm:flex" -> "flex", "hover:bg-blue-500" -> "bg-blue-500"
|
|
271
|
+
*/
|
|
272
|
+
export function extractBaseClass(className) {
|
|
273
|
+
// Remove responsive prefix
|
|
274
|
+
let base = className.replace(/^(sm|md|lg|xl|2xl):/, '');
|
|
275
|
+
// Remove state variant prefix
|
|
276
|
+
base = base.replace(new RegExp(`^(${TAILWIND_STATE_VARIANTS.join('|')}):`, 'i'), '');
|
|
277
|
+
// Remove dark mode prefix
|
|
278
|
+
base = base.replace(/^dark:/, '');
|
|
279
|
+
return base;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Get the responsive breakpoint from a class
|
|
283
|
+
*/
|
|
284
|
+
export function getResponsiveBreakpoint(className) {
|
|
285
|
+
const match = className.match(/^(sm|md|lg|xl|2xl):/);
|
|
286
|
+
return match ? match[1] : null;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Get the breakpoint order index
|
|
290
|
+
*/
|
|
291
|
+
export function getBreakpointOrderIndex(breakpoint) {
|
|
292
|
+
return RESPONSIVE_BREAKPOINT_ORDER.indexOf(breakpoint);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Detect standard Tailwind utility classes in content
|
|
296
|
+
*/
|
|
297
|
+
export function detectUtilityClasses(content, file) {
|
|
298
|
+
const results = [];
|
|
299
|
+
const lines = content.split('\n');
|
|
300
|
+
const seenMatches = new Set();
|
|
301
|
+
for (const pattern of TAILWIND_UTILITY_PATTERNS) {
|
|
302
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
303
|
+
let match;
|
|
304
|
+
while ((match = regex.exec(content)) !== null) {
|
|
305
|
+
const key = `${match.index}-${match[0]}`;
|
|
306
|
+
if (seenMatches.has(key))
|
|
307
|
+
continue;
|
|
308
|
+
seenMatches.add(key);
|
|
309
|
+
// Skip if inside a comment
|
|
310
|
+
if (isInsideComment(content, match.index)) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const beforeMatch = content.slice(0, match.index);
|
|
314
|
+
const lineNumber = beforeMatch.split('\n').length;
|
|
315
|
+
const lastNewline = beforeMatch.lastIndexOf('\n');
|
|
316
|
+
const column = match.index - lastNewline;
|
|
317
|
+
results.push({
|
|
318
|
+
type: 'utility-class',
|
|
319
|
+
file,
|
|
320
|
+
line: lineNumber,
|
|
321
|
+
column,
|
|
322
|
+
matchedText: match[0],
|
|
323
|
+
classNames: [match[0]],
|
|
324
|
+
context: lines[lineNumber - 1] || '',
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return results;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Detect Tailwind arbitrary value classes
|
|
332
|
+
*/
|
|
333
|
+
export function detectArbitraryValues(content, file) {
|
|
334
|
+
const results = [];
|
|
335
|
+
const lines = content.split('\n');
|
|
336
|
+
const regex = new RegExp(TAILWIND_ARBITRARY_VALUE_PATTERN.source, TAILWIND_ARBITRARY_VALUE_PATTERN.flags);
|
|
337
|
+
let match;
|
|
338
|
+
while ((match = regex.exec(content)) !== null) {
|
|
339
|
+
// Skip if inside a comment
|
|
340
|
+
if (isInsideComment(content, match.index)) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const beforeMatch = content.slice(0, match.index);
|
|
344
|
+
const lineNumber = beforeMatch.split('\n').length;
|
|
345
|
+
const lastNewline = beforeMatch.lastIndexOf('\n');
|
|
346
|
+
const column = match.index - lastNewline;
|
|
347
|
+
results.push({
|
|
348
|
+
type: 'arbitrary-value',
|
|
349
|
+
file,
|
|
350
|
+
line: lineNumber,
|
|
351
|
+
column,
|
|
352
|
+
matchedText: match[0],
|
|
353
|
+
classNames: [match[0]],
|
|
354
|
+
context: lines[lineNumber - 1] || '',
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
return results;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Detect Tailwind responsive prefix usage
|
|
361
|
+
*/
|
|
362
|
+
export function detectResponsivePrefixes(content, file) {
|
|
363
|
+
const results = [];
|
|
364
|
+
const lines = content.split('\n');
|
|
365
|
+
const regex = new RegExp(TAILWIND_RESPONSIVE_PATTERN.source, TAILWIND_RESPONSIVE_PATTERN.flags);
|
|
366
|
+
let match;
|
|
367
|
+
while ((match = regex.exec(content)) !== null) {
|
|
368
|
+
// Skip if inside a comment
|
|
369
|
+
if (isInsideComment(content, match.index)) {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
const beforeMatch = content.slice(0, match.index);
|
|
373
|
+
const lineNumber = beforeMatch.split('\n').length;
|
|
374
|
+
const lastNewline = beforeMatch.lastIndexOf('\n');
|
|
375
|
+
const column = match.index - lastNewline;
|
|
376
|
+
results.push({
|
|
377
|
+
type: 'responsive-prefix',
|
|
378
|
+
file,
|
|
379
|
+
line: lineNumber,
|
|
380
|
+
column,
|
|
381
|
+
matchedText: match[0],
|
|
382
|
+
classNames: [match[0]],
|
|
383
|
+
context: lines[lineNumber - 1] || '',
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
return results;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Detect Tailwind state variant usage
|
|
390
|
+
*/
|
|
391
|
+
export function detectStateVariants(content, file) {
|
|
392
|
+
const results = [];
|
|
393
|
+
const lines = content.split('\n');
|
|
394
|
+
const regex = new RegExp(TAILWIND_STATE_VARIANT_PATTERN.source, TAILWIND_STATE_VARIANT_PATTERN.flags);
|
|
395
|
+
let match;
|
|
396
|
+
while ((match = regex.exec(content)) !== null) {
|
|
397
|
+
// Skip if inside a comment
|
|
398
|
+
if (isInsideComment(content, match.index)) {
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
const beforeMatch = content.slice(0, match.index);
|
|
402
|
+
const lineNumber = beforeMatch.split('\n').length;
|
|
403
|
+
const lastNewline = beforeMatch.lastIndexOf('\n');
|
|
404
|
+
const column = match.index - lastNewline;
|
|
405
|
+
results.push({
|
|
406
|
+
type: 'state-variant',
|
|
407
|
+
file,
|
|
408
|
+
line: lineNumber,
|
|
409
|
+
column,
|
|
410
|
+
matchedText: match[0],
|
|
411
|
+
classNames: [match[0]],
|
|
412
|
+
context: lines[lineNumber - 1] || '',
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
return results;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Detect Tailwind dark mode usage
|
|
419
|
+
*/
|
|
420
|
+
export function detectDarkMode(content, file) {
|
|
421
|
+
const results = [];
|
|
422
|
+
const lines = content.split('\n');
|
|
423
|
+
const regex = new RegExp(TAILWIND_DARK_MODE_PATTERN.source, TAILWIND_DARK_MODE_PATTERN.flags);
|
|
424
|
+
let match;
|
|
425
|
+
while ((match = regex.exec(content)) !== null) {
|
|
426
|
+
// Skip if inside a comment
|
|
427
|
+
if (isInsideComment(content, match.index)) {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const beforeMatch = content.slice(0, match.index);
|
|
431
|
+
const lineNumber = beforeMatch.split('\n').length;
|
|
432
|
+
const lastNewline = beforeMatch.lastIndexOf('\n');
|
|
433
|
+
const column = match.index - lastNewline;
|
|
434
|
+
results.push({
|
|
435
|
+
type: 'dark-mode',
|
|
436
|
+
file,
|
|
437
|
+
line: lineNumber,
|
|
438
|
+
column,
|
|
439
|
+
matchedText: match[0],
|
|
440
|
+
classNames: [match[0]],
|
|
441
|
+
context: lines[lineNumber - 1] || '',
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
return results;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Detect @apply directive usage
|
|
448
|
+
*/
|
|
449
|
+
export function detectApplyDirective(content, file) {
|
|
450
|
+
const results = [];
|
|
451
|
+
const lines = content.split('\n');
|
|
452
|
+
const regex = new RegExp(TAILWIND_APPLY_PATTERN.source, TAILWIND_APPLY_PATTERN.flags);
|
|
453
|
+
let match;
|
|
454
|
+
while ((match = regex.exec(content)) !== null) {
|
|
455
|
+
// Skip if inside a comment
|
|
456
|
+
if (isInsideComment(content, match.index)) {
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const beforeMatch = content.slice(0, match.index);
|
|
460
|
+
const lineNumber = beforeMatch.split('\n').length;
|
|
461
|
+
const lastNewline = beforeMatch.lastIndexOf('\n');
|
|
462
|
+
const column = match.index - lastNewline;
|
|
463
|
+
const classes = match[1]?.trim().split(/\s+/) || [];
|
|
464
|
+
results.push({
|
|
465
|
+
type: 'apply-directive',
|
|
466
|
+
file,
|
|
467
|
+
line: lineNumber,
|
|
468
|
+
column,
|
|
469
|
+
matchedText: match[0],
|
|
470
|
+
classNames: classes,
|
|
471
|
+
context: lines[lineNumber - 1] || '',
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
return results;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Detect Tailwind config customizations
|
|
478
|
+
*/
|
|
479
|
+
export function detectConfigCustomizations(content, file) {
|
|
480
|
+
const results = [];
|
|
481
|
+
const lines = content.split('\n');
|
|
482
|
+
// Only check config files
|
|
483
|
+
if (!file.includes('tailwind.config')) {
|
|
484
|
+
return results;
|
|
485
|
+
}
|
|
486
|
+
for (const pattern of TAILWIND_CONFIG_PATTERNS) {
|
|
487
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
488
|
+
let match;
|
|
489
|
+
while ((match = regex.exec(content)) !== null) {
|
|
490
|
+
const beforeMatch = content.slice(0, match.index);
|
|
491
|
+
const lineNumber = beforeMatch.split('\n').length;
|
|
492
|
+
const lastNewline = beforeMatch.lastIndexOf('\n');
|
|
493
|
+
const column = match.index - lastNewline;
|
|
494
|
+
results.push({
|
|
495
|
+
type: 'config-customization',
|
|
496
|
+
file,
|
|
497
|
+
line: lineNumber,
|
|
498
|
+
column,
|
|
499
|
+
matchedText: match[0],
|
|
500
|
+
classNames: [],
|
|
501
|
+
context: lines[lineNumber - 1] || '',
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return results;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Extract all class names from a className attribute
|
|
509
|
+
*/
|
|
510
|
+
function extractClassNamesFromLine(line) {
|
|
511
|
+
const classNames = [];
|
|
512
|
+
// Match className="..." or class="..."
|
|
513
|
+
const classAttrMatch = line.match(/(?:className|class)=["']([^"']+)["']/);
|
|
514
|
+
if (classAttrMatch && classAttrMatch[1]) {
|
|
515
|
+
classNames.push(...classAttrMatch[1].split(/\s+/).filter(Boolean));
|
|
516
|
+
}
|
|
517
|
+
// Match className={`...`} template literals
|
|
518
|
+
const templateMatch = line.match(/(?:className|class)=\{`([^`]+)`\}/);
|
|
519
|
+
if (templateMatch && templateMatch[1]) {
|
|
520
|
+
// Extract static parts (ignore ${...} expressions)
|
|
521
|
+
const staticParts = templateMatch[1].replace(/\$\{[^}]+\}/g, ' ');
|
|
522
|
+
classNames.push(...staticParts.split(/\s+/).filter(Boolean));
|
|
523
|
+
}
|
|
524
|
+
// Match @apply directive
|
|
525
|
+
const applyMatch = line.match(/@apply\s+([^;]+)/);
|
|
526
|
+
if (applyMatch && applyMatch[1]) {
|
|
527
|
+
classNames.push(...applyMatch[1].split(/\s+/).filter(Boolean));
|
|
528
|
+
}
|
|
529
|
+
return classNames;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Detect arbitrary value usage violations
|
|
533
|
+
*/
|
|
534
|
+
export function detectArbitraryValueViolations(arbitraryPatterns, utilityPatterns, file) {
|
|
535
|
+
const results = [];
|
|
536
|
+
for (const arbitrary of arbitraryPatterns) {
|
|
537
|
+
const baseProperty = arbitrary.matchedText.split('-[')[0];
|
|
538
|
+
// Check if there's a standard utility for this property
|
|
539
|
+
const hasStandardAlternative = utilityPatterns.some(utility => {
|
|
540
|
+
const utilityBase = utility.matchedText.split('-')[0];
|
|
541
|
+
return utilityBase === baseProperty;
|
|
542
|
+
});
|
|
543
|
+
// Suggest using standard classes when possible
|
|
544
|
+
const suggestedFix = suggestStandardClass(arbitrary.matchedText);
|
|
545
|
+
const finalSuggestedFix = suggestedFix || (hasStandardAlternative
|
|
546
|
+
? `Consider using a standard Tailwind class for '${baseProperty}'`
|
|
547
|
+
: undefined);
|
|
548
|
+
const violation = {
|
|
549
|
+
type: 'arbitrary-value-usage',
|
|
550
|
+
file,
|
|
551
|
+
line: arbitrary.line,
|
|
552
|
+
column: arbitrary.column,
|
|
553
|
+
endLine: arbitrary.line,
|
|
554
|
+
endColumn: arbitrary.column + arbitrary.matchedText.length,
|
|
555
|
+
classNames: [arbitrary.matchedText],
|
|
556
|
+
issue: `Arbitrary value '${arbitrary.matchedText}' used instead of standard Tailwind class`,
|
|
557
|
+
lineContent: arbitrary.context || '',
|
|
558
|
+
};
|
|
559
|
+
if (finalSuggestedFix !== undefined) {
|
|
560
|
+
violation.suggestedFix = finalSuggestedFix;
|
|
561
|
+
}
|
|
562
|
+
results.push(violation);
|
|
563
|
+
}
|
|
564
|
+
return results;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Suggest a standard Tailwind class for an arbitrary value
|
|
568
|
+
*/
|
|
569
|
+
export function suggestStandardClass(arbitraryClass) {
|
|
570
|
+
const match = arbitraryClass.match(/^([a-z][a-z0-9-]*)-\[([^\]]+)\]$/i);
|
|
571
|
+
if (!match)
|
|
572
|
+
return undefined;
|
|
573
|
+
const [, property, value] = match;
|
|
574
|
+
// Handle pixel values
|
|
575
|
+
const pxMatch = value?.match(/^(\d+)px$/);
|
|
576
|
+
if (pxMatch && property) {
|
|
577
|
+
const px = parseInt(pxMatch[1], 10);
|
|
578
|
+
// Tailwind uses 4px = 1 unit
|
|
579
|
+
const tailwindUnit = px / 4;
|
|
580
|
+
if (Number.isInteger(tailwindUnit) && tailwindUnit >= 0 && tailwindUnit <= 96) {
|
|
581
|
+
return `${property}-${tailwindUnit}`;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
// Handle rem values
|
|
585
|
+
const remMatch = value?.match(/^(\d+(?:\.\d+)?)rem$/);
|
|
586
|
+
if (remMatch && property) {
|
|
587
|
+
const rem = parseFloat(remMatch[1]);
|
|
588
|
+
// Tailwind uses 0.25rem = 1 unit
|
|
589
|
+
const tailwindUnit = rem / 0.25;
|
|
590
|
+
if (Number.isInteger(tailwindUnit) && tailwindUnit >= 0 && tailwindUnit <= 96) {
|
|
591
|
+
return `${property}-${tailwindUnit}`;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
// Handle percentage values for width/height
|
|
595
|
+
if ((property === 'w' || property === 'h') && value) {
|
|
596
|
+
if (value === '100%')
|
|
597
|
+
return `${property}-full`;
|
|
598
|
+
if (value === '50%')
|
|
599
|
+
return `${property}-1/2`;
|
|
600
|
+
if (value === '33.333333%' || value === '33.33%')
|
|
601
|
+
return `${property}-1/3`;
|
|
602
|
+
if (value === '66.666667%' || value === '66.67%')
|
|
603
|
+
return `${property}-2/3`;
|
|
604
|
+
if (value === '25%')
|
|
605
|
+
return `${property}-1/4`;
|
|
606
|
+
if (value === '75%')
|
|
607
|
+
return `${property}-3/4`;
|
|
608
|
+
}
|
|
609
|
+
return undefined;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Detect conflicting class violations
|
|
613
|
+
*/
|
|
614
|
+
export function detectConflictingClasses(content, file) {
|
|
615
|
+
const results = [];
|
|
616
|
+
const lines = content.split('\n');
|
|
617
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
618
|
+
const line = lines[lineIndex];
|
|
619
|
+
const classNames = extractClassNamesFromLine(line);
|
|
620
|
+
if (classNames.length === 0)
|
|
621
|
+
continue;
|
|
622
|
+
// Check for conflicting pairs
|
|
623
|
+
for (const [pattern1, pattern2, description] of CONFLICTING_CLASS_PAIRS) {
|
|
624
|
+
const hasFirst = classNames.some(cn => pattern1.test(cn));
|
|
625
|
+
const hasSecond = classNames.some(cn => pattern2.test(cn));
|
|
626
|
+
if (hasFirst && hasSecond) {
|
|
627
|
+
const conflictingClasses = classNames.filter(cn => pattern1.test(cn) || pattern2.test(cn));
|
|
628
|
+
// Find the position of the first conflicting class
|
|
629
|
+
const firstClass = conflictingClasses[0];
|
|
630
|
+
const classIndex = line.indexOf(firstClass);
|
|
631
|
+
const column = classIndex >= 0 ? classIndex + 1 : 1;
|
|
632
|
+
results.push({
|
|
633
|
+
type: 'conflicting-classes',
|
|
634
|
+
file,
|
|
635
|
+
line: lineIndex + 1,
|
|
636
|
+
column,
|
|
637
|
+
endLine: lineIndex + 1,
|
|
638
|
+
endColumn: column + conflictingClasses.join(' ').length,
|
|
639
|
+
classNames: conflictingClasses,
|
|
640
|
+
issue: `Conflicting classes: ${description}`,
|
|
641
|
+
suggestedFix: `Remove one of the conflicting classes: ${conflictingClasses.join(', ')}`,
|
|
642
|
+
lineContent: line,
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return results;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Detect inconsistent responsive breakpoint ordering
|
|
651
|
+
*/
|
|
652
|
+
export function detectInconsistentBreakpointOrder(content, file) {
|
|
653
|
+
const results = [];
|
|
654
|
+
const lines = content.split('\n');
|
|
655
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
656
|
+
const line = lines[lineIndex];
|
|
657
|
+
const classNames = extractClassNamesFromLine(line);
|
|
658
|
+
if (classNames.length === 0)
|
|
659
|
+
continue;
|
|
660
|
+
// Group classes by their property prefix (e.g., 'p' for p-4, 'flex' for flex)
|
|
661
|
+
const classGroups = new Map();
|
|
662
|
+
classNames.forEach((className, index) => {
|
|
663
|
+
const breakpoint = getResponsiveBreakpoint(className);
|
|
664
|
+
const baseClass = extractBaseClass(className);
|
|
665
|
+
// Extract property prefix (e.g., 'p' from 'p-4', 'flex' from 'flex')
|
|
666
|
+
const propertyMatch = baseClass.match(/^([a-z]+(?:-[a-z]+)*?)(?:-\d|$)/i);
|
|
667
|
+
const propertyPrefix = propertyMatch ? propertyMatch[1] : baseClass;
|
|
668
|
+
if (!classGroups.has(propertyPrefix)) {
|
|
669
|
+
classGroups.set(propertyPrefix, []);
|
|
670
|
+
}
|
|
671
|
+
classGroups.get(propertyPrefix).push({ className, breakpoint, index });
|
|
672
|
+
});
|
|
673
|
+
// Check each group for proper ordering
|
|
674
|
+
for (const [propertyPrefix, group] of classGroups) {
|
|
675
|
+
const responsiveClasses = group.filter(g => g.breakpoint !== null);
|
|
676
|
+
if (responsiveClasses.length < 2)
|
|
677
|
+
continue;
|
|
678
|
+
// Check if breakpoints are in order (by their position in the class list)
|
|
679
|
+
// Sort by their original index to check order
|
|
680
|
+
const sortedByIndex = [...responsiveClasses].sort((a, b) => a.index - b.index);
|
|
681
|
+
let lastBreakpointIndex = -1;
|
|
682
|
+
let isOutOfOrder = false;
|
|
683
|
+
let outOfOrderClasses = [];
|
|
684
|
+
for (const { breakpoint } of sortedByIndex) {
|
|
685
|
+
const breakpointIndex = getBreakpointOrderIndex(breakpoint);
|
|
686
|
+
if (breakpointIndex < lastBreakpointIndex) {
|
|
687
|
+
isOutOfOrder = true;
|
|
688
|
+
outOfOrderClasses = sortedByIndex.map(r => r.className);
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
lastBreakpointIndex = breakpointIndex;
|
|
692
|
+
}
|
|
693
|
+
if (isOutOfOrder) {
|
|
694
|
+
const firstClass = outOfOrderClasses[0];
|
|
695
|
+
const classIndex = line.indexOf(firstClass);
|
|
696
|
+
const column = classIndex >= 0 ? classIndex + 1 : 1;
|
|
697
|
+
// Sort classes by breakpoint order
|
|
698
|
+
const sortedClasses = [...responsiveClasses]
|
|
699
|
+
.sort((a, b) => getBreakpointOrderIndex(a.breakpoint) - getBreakpointOrderIndex(b.breakpoint))
|
|
700
|
+
.map(r => r.className);
|
|
701
|
+
results.push({
|
|
702
|
+
type: 'inconsistent-breakpoint-order',
|
|
703
|
+
file,
|
|
704
|
+
line: lineIndex + 1,
|
|
705
|
+
column,
|
|
706
|
+
endLine: lineIndex + 1,
|
|
707
|
+
endColumn: column + outOfOrderClasses.join(' ').length,
|
|
708
|
+
classNames: outOfOrderClasses,
|
|
709
|
+
issue: `Responsive breakpoints for '${propertyPrefix}' are not in mobile-first order (sm → md → lg → xl → 2xl)`,
|
|
710
|
+
suggestedFix: `Reorder to: ${sortedClasses.join(' ')}`,
|
|
711
|
+
lineContent: line,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return results;
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Detect mixed arbitrary and standard values for the same property
|
|
720
|
+
*/
|
|
721
|
+
export function detectMixedArbitraryStandard(arbitraryPatterns, utilityPatterns, file) {
|
|
722
|
+
const results = [];
|
|
723
|
+
// Group patterns by property prefix
|
|
724
|
+
const arbitraryByProperty = new Map();
|
|
725
|
+
const standardByProperty = new Map();
|
|
726
|
+
for (const pattern of arbitraryPatterns) {
|
|
727
|
+
const property = pattern.matchedText.split('-[')[0] || '';
|
|
728
|
+
if (!arbitraryByProperty.has(property)) {
|
|
729
|
+
arbitraryByProperty.set(property, []);
|
|
730
|
+
}
|
|
731
|
+
arbitraryByProperty.get(property).push(pattern);
|
|
732
|
+
}
|
|
733
|
+
for (const pattern of utilityPatterns) {
|
|
734
|
+
const property = pattern.matchedText.split('-')[0] || '';
|
|
735
|
+
if (!standardByProperty.has(property)) {
|
|
736
|
+
standardByProperty.set(property, []);
|
|
737
|
+
}
|
|
738
|
+
standardByProperty.get(property).push(pattern);
|
|
739
|
+
}
|
|
740
|
+
// Find properties that have both arbitrary and standard values
|
|
741
|
+
for (const [property, arbitraryList] of arbitraryByProperty) {
|
|
742
|
+
if (standardByProperty.has(property)) {
|
|
743
|
+
const standardList = standardByProperty.get(property);
|
|
744
|
+
// Only flag if there are multiple instances suggesting inconsistency
|
|
745
|
+
if (arbitraryList.length > 0 && standardList.length > 0) {
|
|
746
|
+
const firstArbitrary = arbitraryList[0];
|
|
747
|
+
results.push({
|
|
748
|
+
type: 'mixed-arbitrary-standard',
|
|
749
|
+
file,
|
|
750
|
+
line: firstArbitrary.line,
|
|
751
|
+
column: firstArbitrary.column,
|
|
752
|
+
endLine: firstArbitrary.line,
|
|
753
|
+
endColumn: firstArbitrary.column + firstArbitrary.matchedText.length,
|
|
754
|
+
classNames: [
|
|
755
|
+
...arbitraryList.map(p => p.matchedText),
|
|
756
|
+
...standardList.map(p => p.matchedText),
|
|
757
|
+
],
|
|
758
|
+
issue: `Mixed arbitrary and standard values for '${property}' property`,
|
|
759
|
+
suggestedFix: `Consider using consistent values - either all standard Tailwind classes or all arbitrary values for '${property}'`,
|
|
760
|
+
lineContent: firstArbitrary.context || '',
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return results;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Analyze Tailwind patterns in a file
|
|
769
|
+
*/
|
|
770
|
+
export function analyzeTailwindPatterns(content, file) {
|
|
771
|
+
// Skip excluded files
|
|
772
|
+
if (shouldExcludeFile(file)) {
|
|
773
|
+
return {
|
|
774
|
+
patterns: [],
|
|
775
|
+
violations: [],
|
|
776
|
+
usesUtilityClasses: false,
|
|
777
|
+
usesArbitraryValues: false,
|
|
778
|
+
usesResponsivePrefixes: false,
|
|
779
|
+
usesStateVariants: false,
|
|
780
|
+
usesDarkMode: false,
|
|
781
|
+
usesApplyDirective: false,
|
|
782
|
+
tailwindConsistencyConfidence: 1.0,
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
// Detect all patterns
|
|
786
|
+
const utilityClasses = detectUtilityClasses(content, file);
|
|
787
|
+
const arbitraryValues = detectArbitraryValues(content, file);
|
|
788
|
+
const responsivePrefixes = detectResponsivePrefixes(content, file);
|
|
789
|
+
const stateVariants = detectStateVariants(content, file);
|
|
790
|
+
const darkMode = detectDarkMode(content, file);
|
|
791
|
+
const applyDirective = detectApplyDirective(content, file);
|
|
792
|
+
const configCustomizations = detectConfigCustomizations(content, file);
|
|
793
|
+
const allPatterns = [
|
|
794
|
+
...utilityClasses,
|
|
795
|
+
...arbitraryValues,
|
|
796
|
+
...responsivePrefixes,
|
|
797
|
+
...stateVariants,
|
|
798
|
+
...darkMode,
|
|
799
|
+
...applyDirective,
|
|
800
|
+
...configCustomizations,
|
|
801
|
+
];
|
|
802
|
+
// Detect violations
|
|
803
|
+
const arbitraryViolations = detectArbitraryValueViolations(arbitraryValues, utilityClasses, file);
|
|
804
|
+
const conflictingViolations = detectConflictingClasses(content, file);
|
|
805
|
+
const breakpointViolations = detectInconsistentBreakpointOrder(content, file);
|
|
806
|
+
const mixedViolations = detectMixedArbitraryStandard(arbitraryValues, utilityClasses, file);
|
|
807
|
+
const allViolations = [
|
|
808
|
+
...arbitraryViolations,
|
|
809
|
+
...conflictingViolations,
|
|
810
|
+
...breakpointViolations,
|
|
811
|
+
...mixedViolations,
|
|
812
|
+
];
|
|
813
|
+
// Calculate confidence
|
|
814
|
+
const hasPatterns = allPatterns.length > 0;
|
|
815
|
+
const hasViolations = allViolations.length > 0;
|
|
816
|
+
let tailwindConsistencyConfidence = 0;
|
|
817
|
+
if (hasPatterns && !hasViolations) {
|
|
818
|
+
tailwindConsistencyConfidence = 1.0;
|
|
819
|
+
}
|
|
820
|
+
else if (hasPatterns && hasViolations) {
|
|
821
|
+
const ratio = allPatterns.length / (allPatterns.length + allViolations.length);
|
|
822
|
+
tailwindConsistencyConfidence = ratio;
|
|
823
|
+
}
|
|
824
|
+
else if (!hasPatterns && hasViolations) {
|
|
825
|
+
tailwindConsistencyConfidence = 0;
|
|
826
|
+
}
|
|
827
|
+
else {
|
|
828
|
+
tailwindConsistencyConfidence = 0.5; // No Tailwind detected
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
patterns: allPatterns,
|
|
832
|
+
violations: allViolations,
|
|
833
|
+
usesUtilityClasses: utilityClasses.length > 0,
|
|
834
|
+
usesArbitraryValues: arbitraryValues.length > 0,
|
|
835
|
+
usesResponsivePrefixes: responsivePrefixes.length > 0,
|
|
836
|
+
usesStateVariants: stateVariants.length > 0,
|
|
837
|
+
usesDarkMode: darkMode.length > 0,
|
|
838
|
+
usesApplyDirective: applyDirective.length > 0,
|
|
839
|
+
tailwindConsistencyConfidence,
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
// ============================================================================
|
|
843
|
+
// Tailwind Patterns Detector Class
|
|
844
|
+
// ============================================================================
|
|
845
|
+
/**
|
|
846
|
+
* Detector for Tailwind CSS pattern consistency
|
|
847
|
+
*
|
|
848
|
+
* Identifies Tailwind patterns and flags inconsistent usage.
|
|
849
|
+
*
|
|
850
|
+
* @requirements 9.6 - THE Styling_Detector SHALL detect Tailwind pattern consistency
|
|
851
|
+
*/
|
|
852
|
+
export class TailwindPatternsDetector extends RegexDetector {
|
|
853
|
+
id = 'styling/tailwind-patterns';
|
|
854
|
+
category = 'styling';
|
|
855
|
+
subcategory = 'tailwind-patterns';
|
|
856
|
+
name = 'Tailwind Patterns Detector';
|
|
857
|
+
description = 'Detects Tailwind CSS patterns and flags inconsistent usage';
|
|
858
|
+
supportedLanguages = ['typescript', 'javascript', 'css'];
|
|
859
|
+
/**
|
|
860
|
+
* Detect Tailwind patterns and violations
|
|
861
|
+
*/
|
|
862
|
+
async detect(context) {
|
|
863
|
+
const patterns = [];
|
|
864
|
+
const violations = [];
|
|
865
|
+
// Analyze the file
|
|
866
|
+
const analysis = analyzeTailwindPatterns(context.content, context.file);
|
|
867
|
+
// Create pattern matches for Tailwind patterns
|
|
868
|
+
if (analysis.usesUtilityClasses) {
|
|
869
|
+
patterns.push(this.createUtilityClassPattern(context.file, analysis));
|
|
870
|
+
}
|
|
871
|
+
if (analysis.usesArbitraryValues) {
|
|
872
|
+
patterns.push(this.createArbitraryValuePattern(context.file, analysis));
|
|
873
|
+
}
|
|
874
|
+
if (analysis.usesResponsivePrefixes) {
|
|
875
|
+
patterns.push(this.createResponsivePrefixPattern(context.file, analysis));
|
|
876
|
+
}
|
|
877
|
+
if (analysis.usesStateVariants) {
|
|
878
|
+
patterns.push(this.createStateVariantPattern(context.file, analysis));
|
|
879
|
+
}
|
|
880
|
+
if (analysis.usesDarkMode) {
|
|
881
|
+
patterns.push(this.createDarkModePattern(context.file, analysis));
|
|
882
|
+
}
|
|
883
|
+
if (analysis.usesApplyDirective) {
|
|
884
|
+
patterns.push(this.createApplyDirectivePattern(context.file, analysis));
|
|
885
|
+
}
|
|
886
|
+
// Create violations
|
|
887
|
+
for (const violation of analysis.violations) {
|
|
888
|
+
violations.push(this.createTailwindViolation(violation));
|
|
889
|
+
}
|
|
890
|
+
return this.createResult(patterns, violations, analysis.tailwindConsistencyConfidence);
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Create a pattern match for utility class usage
|
|
894
|
+
*/
|
|
895
|
+
createUtilityClassPattern(file, analysis) {
|
|
896
|
+
const utilityPatterns = analysis.patterns.filter(p => p.type === 'utility-class');
|
|
897
|
+
const firstPattern = utilityPatterns[0];
|
|
898
|
+
return {
|
|
899
|
+
patternId: `${this.id}/utility-class`,
|
|
900
|
+
location: {
|
|
901
|
+
file,
|
|
902
|
+
line: firstPattern?.line || 1,
|
|
903
|
+
column: firstPattern?.column || 1,
|
|
904
|
+
},
|
|
905
|
+
confidence: 1.0,
|
|
906
|
+
isOutlier: false,
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Create a pattern match for arbitrary value usage
|
|
911
|
+
*/
|
|
912
|
+
createArbitraryValuePattern(file, analysis) {
|
|
913
|
+
const arbitraryPatterns = analysis.patterns.filter(p => p.type === 'arbitrary-value');
|
|
914
|
+
const firstPattern = arbitraryPatterns[0];
|
|
915
|
+
return {
|
|
916
|
+
patternId: `${this.id}/arbitrary-value`,
|
|
917
|
+
location: {
|
|
918
|
+
file,
|
|
919
|
+
line: firstPattern?.line || 1,
|
|
920
|
+
column: firstPattern?.column || 1,
|
|
921
|
+
},
|
|
922
|
+
confidence: 0.7, // Lower confidence for arbitrary values
|
|
923
|
+
isOutlier: true, // Arbitrary values are considered outliers
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Create a pattern match for responsive prefix usage
|
|
928
|
+
*/
|
|
929
|
+
createResponsivePrefixPattern(file, analysis) {
|
|
930
|
+
const responsivePatterns = analysis.patterns.filter(p => p.type === 'responsive-prefix');
|
|
931
|
+
const firstPattern = responsivePatterns[0];
|
|
932
|
+
return {
|
|
933
|
+
patternId: `${this.id}/responsive-prefix`,
|
|
934
|
+
location: {
|
|
935
|
+
file,
|
|
936
|
+
line: firstPattern?.line || 1,
|
|
937
|
+
column: firstPattern?.column || 1,
|
|
938
|
+
},
|
|
939
|
+
confidence: 1.0,
|
|
940
|
+
isOutlier: false,
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Create a pattern match for state variant usage
|
|
945
|
+
*/
|
|
946
|
+
createStateVariantPattern(file, analysis) {
|
|
947
|
+
const statePatterns = analysis.patterns.filter(p => p.type === 'state-variant');
|
|
948
|
+
const firstPattern = statePatterns[0];
|
|
949
|
+
return {
|
|
950
|
+
patternId: `${this.id}/state-variant`,
|
|
951
|
+
location: {
|
|
952
|
+
file,
|
|
953
|
+
line: firstPattern?.line || 1,
|
|
954
|
+
column: firstPattern?.column || 1,
|
|
955
|
+
},
|
|
956
|
+
confidence: 1.0,
|
|
957
|
+
isOutlier: false,
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Create a pattern match for dark mode usage
|
|
962
|
+
*/
|
|
963
|
+
createDarkModePattern(file, analysis) {
|
|
964
|
+
const darkModePatterns = analysis.patterns.filter(p => p.type === 'dark-mode');
|
|
965
|
+
const firstPattern = darkModePatterns[0];
|
|
966
|
+
return {
|
|
967
|
+
patternId: `${this.id}/dark-mode`,
|
|
968
|
+
location: {
|
|
969
|
+
file,
|
|
970
|
+
line: firstPattern?.line || 1,
|
|
971
|
+
column: firstPattern?.column || 1,
|
|
972
|
+
},
|
|
973
|
+
confidence: 1.0,
|
|
974
|
+
isOutlier: false,
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Create a pattern match for @apply directive usage
|
|
979
|
+
*/
|
|
980
|
+
createApplyDirectivePattern(file, analysis) {
|
|
981
|
+
const applyPatterns = analysis.patterns.filter(p => p.type === 'apply-directive');
|
|
982
|
+
const firstPattern = applyPatterns[0];
|
|
983
|
+
return {
|
|
984
|
+
patternId: `${this.id}/apply-directive`,
|
|
985
|
+
location: {
|
|
986
|
+
file,
|
|
987
|
+
line: firstPattern?.line || 1,
|
|
988
|
+
column: firstPattern?.column || 1,
|
|
989
|
+
},
|
|
990
|
+
confidence: 1.0,
|
|
991
|
+
isOutlier: false,
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Create a violation for a Tailwind issue
|
|
996
|
+
*/
|
|
997
|
+
createTailwindViolation(info) {
|
|
998
|
+
const typeDescriptions = {
|
|
999
|
+
'arbitrary-value-usage': 'Arbitrary value usage',
|
|
1000
|
+
'inconsistent-breakpoint-order': 'Inconsistent breakpoint order',
|
|
1001
|
+
'conflicting-classes': 'Conflicting classes',
|
|
1002
|
+
'non-standard-ordering': 'Non-standard class ordering',
|
|
1003
|
+
'mixed-arbitrary-standard': 'Mixed arbitrary and standard values',
|
|
1004
|
+
};
|
|
1005
|
+
const typeDescription = typeDescriptions[info.type] || 'Tailwind issue';
|
|
1006
|
+
const severity = this.getSeverityForViolationType(info.type);
|
|
1007
|
+
const violation = {
|
|
1008
|
+
id: `${this.id}-${info.file}-${info.line}-${info.column}`,
|
|
1009
|
+
patternId: this.id,
|
|
1010
|
+
severity,
|
|
1011
|
+
file: info.file,
|
|
1012
|
+
range: {
|
|
1013
|
+
start: { line: info.line - 1, character: info.column - 1 },
|
|
1014
|
+
end: { line: info.endLine - 1, character: info.endColumn - 1 },
|
|
1015
|
+
},
|
|
1016
|
+
message: `${typeDescription}: ${info.issue}`,
|
|
1017
|
+
explanation: this.getExplanationForViolationType(info.type),
|
|
1018
|
+
expected: info.suggestedFix || 'Consistent Tailwind usage',
|
|
1019
|
+
actual: info.classNames.join(', '),
|
|
1020
|
+
aiExplainAvailable: true,
|
|
1021
|
+
aiFixAvailable: info.type === 'conflicting-classes' || info.type === 'inconsistent-breakpoint-order',
|
|
1022
|
+
firstSeen: new Date(),
|
|
1023
|
+
occurrences: 1,
|
|
1024
|
+
};
|
|
1025
|
+
const quickFix = this.createQuickFixForViolation(info);
|
|
1026
|
+
if (quickFix !== undefined) {
|
|
1027
|
+
violation.quickFix = quickFix;
|
|
1028
|
+
}
|
|
1029
|
+
return violation;
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Get severity for a violation type
|
|
1033
|
+
*/
|
|
1034
|
+
getSeverityForViolationType(type) {
|
|
1035
|
+
switch (type) {
|
|
1036
|
+
case 'conflicting-classes':
|
|
1037
|
+
return 'warning';
|
|
1038
|
+
case 'inconsistent-breakpoint-order':
|
|
1039
|
+
return 'info';
|
|
1040
|
+
case 'arbitrary-value-usage':
|
|
1041
|
+
return 'info';
|
|
1042
|
+
case 'mixed-arbitrary-standard':
|
|
1043
|
+
return 'info';
|
|
1044
|
+
case 'non-standard-ordering':
|
|
1045
|
+
return 'hint';
|
|
1046
|
+
default:
|
|
1047
|
+
return 'info';
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Get explanation for a violation type
|
|
1052
|
+
*/
|
|
1053
|
+
getExplanationForViolationType(type) {
|
|
1054
|
+
switch (type) {
|
|
1055
|
+
case 'arbitrary-value-usage':
|
|
1056
|
+
return 'Using arbitrary values (e.g., p-[13px]) instead of standard Tailwind classes can lead to inconsistent spacing and make the design system harder to maintain. Consider using standard Tailwind spacing values.';
|
|
1057
|
+
case 'inconsistent-breakpoint-order':
|
|
1058
|
+
return 'Tailwind uses a mobile-first approach. Responsive classes should be ordered from smallest to largest breakpoint (sm → md → lg → xl → 2xl) for better readability and maintainability.';
|
|
1059
|
+
case 'conflicting-classes':
|
|
1060
|
+
return 'These classes conflict with each other and may cause unexpected behavior. Only one of these classes should be applied at a time.';
|
|
1061
|
+
case 'non-standard-ordering':
|
|
1062
|
+
return 'Following a consistent class ordering convention improves readability. Consider ordering classes by: layout → position → sizing → spacing → border → background → typography → effects.';
|
|
1063
|
+
case 'mixed-arbitrary-standard':
|
|
1064
|
+
return 'Mixing arbitrary values with standard Tailwind classes for the same property can lead to inconsistent styling. Consider using either all standard classes or all arbitrary values consistently.';
|
|
1065
|
+
default:
|
|
1066
|
+
return 'Consistent Tailwind usage improves code maintainability and design system adherence.';
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Create a quick fix for a Tailwind violation
|
|
1071
|
+
*/
|
|
1072
|
+
createQuickFixForViolation(info) {
|
|
1073
|
+
if (info.type === 'inconsistent-breakpoint-order' && info.suggestedFix) {
|
|
1074
|
+
return {
|
|
1075
|
+
title: `Reorder breakpoints: ${info.suggestedFix}`,
|
|
1076
|
+
kind: 'quickfix',
|
|
1077
|
+
edit: {
|
|
1078
|
+
changes: {
|
|
1079
|
+
[info.file]: [
|
|
1080
|
+
{
|
|
1081
|
+
range: {
|
|
1082
|
+
start: { line: info.line - 1, character: info.column - 1 },
|
|
1083
|
+
end: { line: info.endLine - 1, character: info.endColumn - 1 },
|
|
1084
|
+
},
|
|
1085
|
+
newText: info.suggestedFix,
|
|
1086
|
+
},
|
|
1087
|
+
],
|
|
1088
|
+
},
|
|
1089
|
+
},
|
|
1090
|
+
isPreferred: true,
|
|
1091
|
+
confidence: 0.9,
|
|
1092
|
+
preview: `Reorder to: ${info.suggestedFix}`,
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
if (info.type === 'arbitrary-value-usage' && info.suggestedFix) {
|
|
1096
|
+
const standardClass = suggestStandardClass(info.classNames[0] || '');
|
|
1097
|
+
if (standardClass) {
|
|
1098
|
+
return {
|
|
1099
|
+
title: `Use standard class: ${standardClass}`,
|
|
1100
|
+
kind: 'quickfix',
|
|
1101
|
+
edit: {
|
|
1102
|
+
changes: {
|
|
1103
|
+
[info.file]: [
|
|
1104
|
+
{
|
|
1105
|
+
range: {
|
|
1106
|
+
start: { line: info.line - 1, character: info.column - 1 },
|
|
1107
|
+
end: { line: info.endLine - 1, character: info.endColumn - 1 },
|
|
1108
|
+
},
|
|
1109
|
+
newText: standardClass,
|
|
1110
|
+
},
|
|
1111
|
+
],
|
|
1112
|
+
},
|
|
1113
|
+
},
|
|
1114
|
+
isPreferred: true,
|
|
1115
|
+
confidence: 0.8,
|
|
1116
|
+
preview: `Replace '${info.classNames[0]}' with '${standardClass}'`,
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
return undefined;
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Generate a quick fix for a violation
|
|
1124
|
+
*/
|
|
1125
|
+
generateQuickFix(violation) {
|
|
1126
|
+
// Check if this is a breakpoint order violation
|
|
1127
|
+
if (violation.message.includes('breakpoint order') && violation.expected !== 'Consistent Tailwind usage') {
|
|
1128
|
+
return {
|
|
1129
|
+
title: `Reorder breakpoints`,
|
|
1130
|
+
kind: 'quickfix',
|
|
1131
|
+
edit: {
|
|
1132
|
+
changes: {
|
|
1133
|
+
[violation.file]: [
|
|
1134
|
+
{
|
|
1135
|
+
range: violation.range,
|
|
1136
|
+
newText: violation.expected,
|
|
1137
|
+
},
|
|
1138
|
+
],
|
|
1139
|
+
},
|
|
1140
|
+
},
|
|
1141
|
+
isPreferred: true,
|
|
1142
|
+
confidence: 0.9,
|
|
1143
|
+
preview: `Reorder to: ${violation.expected}`,
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
// Check if this is an arbitrary value violation with a suggestion
|
|
1147
|
+
if (violation.message.includes('Arbitrary value') && violation.actual) {
|
|
1148
|
+
const standardClass = suggestStandardClass(violation.actual);
|
|
1149
|
+
if (standardClass) {
|
|
1150
|
+
return {
|
|
1151
|
+
title: `Use standard class: ${standardClass}`,
|
|
1152
|
+
kind: 'quickfix',
|
|
1153
|
+
edit: {
|
|
1154
|
+
changes: {
|
|
1155
|
+
[violation.file]: [
|
|
1156
|
+
{
|
|
1157
|
+
range: violation.range,
|
|
1158
|
+
newText: standardClass,
|
|
1159
|
+
},
|
|
1160
|
+
],
|
|
1161
|
+
},
|
|
1162
|
+
},
|
|
1163
|
+
isPreferred: true,
|
|
1164
|
+
confidence: 0.8,
|
|
1165
|
+
preview: `Replace '${violation.actual}' with '${standardClass}'`,
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
return null;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
// ============================================================================
|
|
1173
|
+
// Factory Function
|
|
1174
|
+
// ============================================================================
|
|
1175
|
+
/**
|
|
1176
|
+
* Create a new TailwindPatternsDetector instance
|
|
1177
|
+
*/
|
|
1178
|
+
export function createTailwindPatternsDetector() {
|
|
1179
|
+
return new TailwindPatternsDetector();
|
|
1180
|
+
}
|
|
1181
|
+
//# sourceMappingURL=tailwind-patterns.js.map
|