@codyswann/lisa 1.0.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/LICENSE +21 -0
- package/README.md +867 -0
- package/all/copy-overwrite/.claude/README.md +205 -0
- package/all/copy-overwrite/.claude/agents/agent-architect.md +311 -0
- package/all/copy-overwrite/.claude/agents/codebase-analyzer.md +146 -0
- package/all/copy-overwrite/.claude/agents/codebase-locator.md +125 -0
- package/all/copy-overwrite/.claude/agents/codebase-pattern-finder.md +237 -0
- package/all/copy-overwrite/.claude/agents/git-history-analyzer.md +183 -0
- package/all/copy-overwrite/.claude/agents/hooks-expert.md +74 -0
- package/all/copy-overwrite/.claude/agents/skill-evaluator.md +246 -0
- package/all/copy-overwrite/.claude/agents/slash-command-architect.md +87 -0
- package/all/copy-overwrite/.claude/agents/web-search-researcher.md +112 -0
- package/all/copy-overwrite/.claude/commands/git/commit-and-submit-pr.md +8 -0
- package/all/copy-overwrite/.claude/commands/git/commit.md +44 -0
- package/all/copy-overwrite/.claude/commands/git/prune.md +34 -0
- package/all/copy-overwrite/.claude/commands/git/submit-pr.md +50 -0
- package/all/copy-overwrite/.claude/commands/jira/create.md +50 -0
- package/all/copy-overwrite/.claude/commands/jira/verify.md +34 -0
- package/all/copy-overwrite/.claude/commands/project/archive.md +8 -0
- package/all/copy-overwrite/.claude/commands/project/bootstrap.md +49 -0
- package/all/copy-overwrite/.claude/commands/project/complete-task.md +7 -0
- package/all/copy-overwrite/.claude/commands/project/debrief.md +65 -0
- package/all/copy-overwrite/.claude/commands/project/execute.md +94 -0
- package/all/copy-overwrite/.claude/commands/project/implement.md +42 -0
- package/all/copy-overwrite/.claude/commands/project/local-code-review.md +88 -0
- package/all/copy-overwrite/.claude/commands/project/lower-code-complexity.md +74 -0
- package/all/copy-overwrite/.claude/commands/project/plan.md +314 -0
- package/all/copy-overwrite/.claude/commands/project/research.md +248 -0
- package/all/copy-overwrite/.claude/commands/project/review.md +63 -0
- package/all/copy-overwrite/.claude/commands/project/setup.md +19 -0
- package/all/copy-overwrite/.claude/commands/project/verify.md +38 -0
- package/all/copy-overwrite/.claude/commands/pull-request/review.md +12 -0
- package/all/copy-overwrite/.claude/commands/rules/format-md.md +72 -0
- package/all/copy-overwrite/.claude/commands/sonarqube/check.md +6 -0
- package/all/copy-overwrite/.claude/commands/sonarqube/fix.md +3 -0
- package/all/copy-overwrite/.claude/hooks/README.md +301 -0
- package/all/copy-overwrite/.claude/hooks/notify-ntfy.sh +181 -0
- package/all/copy-overwrite/.claude/settings.json +41 -0
- package/all/copy-overwrite/.claude/settings.local.json.example +14 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/SKILL.md +405 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/references/function-structure.md +416 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/references/immutable-patterns.md +316 -0
- package/all/copy-overwrite/.claude/skills/prompt-complexity-scorer/SKILL.md +118 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/LICENSE.txt +202 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/SKILL.md +210 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/__pycache__/quick_validate.cpython-312.pyc +0 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/all/copy-overwrite/CLAUDE.md +77 -0
- package/all/copy-overwrite/HUMAN.md +17 -0
- package/all/copy-overwrite/specs/.keep +0 -0
- package/all/create-only/PROJECT_RULES.md +0 -0
- package/cdk/merge/package.json +20 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +107 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/prompts.d.ts +45 -0
- package/dist/cli/prompts.d.ts.map +1 -0
- package/dist/cli/prompts.js +58 -0
- package/dist/cli/prompts.js.map +1 -0
- package/dist/core/config.d.ts +73 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +36 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/lisa.d.ts +81 -0
- package/dist/core/lisa.d.ts.map +1 -0
- package/dist/core/lisa.js +459 -0
- package/dist/core/lisa.js.map +1 -0
- package/dist/core/manifest.d.ts +58 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +104 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/detection/detector.interface.d.ts +15 -0
- package/dist/detection/detector.interface.d.ts.map +1 -0
- package/dist/detection/detector.interface.js +2 -0
- package/dist/detection/detector.interface.js.map +1 -0
- package/dist/detection/detectors/cdk.d.ts +10 -0
- package/dist/detection/detectors/cdk.d.ts.map +1 -0
- package/dist/detection/detectors/cdk.js +34 -0
- package/dist/detection/detectors/cdk.js.map +1 -0
- package/dist/detection/detectors/expo.d.ts +10 -0
- package/dist/detection/detectors/expo.d.ts.map +1 -0
- package/dist/detection/detectors/expo.js +30 -0
- package/dist/detection/detectors/expo.js.map +1 -0
- package/dist/detection/detectors/nestjs.d.ts +10 -0
- package/dist/detection/detectors/nestjs.d.ts.map +1 -0
- package/dist/detection/detectors/nestjs.js +34 -0
- package/dist/detection/detectors/nestjs.js.map +1 -0
- package/dist/detection/detectors/npm-package.d.ts +13 -0
- package/dist/detection/detectors/npm-package.d.ts.map +1 -0
- package/dist/detection/detectors/npm-package.js +30 -0
- package/dist/detection/detectors/npm-package.js.map +1 -0
- package/dist/detection/detectors/typescript.d.ts +10 -0
- package/dist/detection/detectors/typescript.d.ts.map +1 -0
- package/dist/detection/detectors/typescript.js +25 -0
- package/dist/detection/detectors/typescript.js.map +1 -0
- package/dist/detection/index.d.ts +24 -0
- package/dist/detection/index.d.ts.map +1 -0
- package/dist/detection/index.js +57 -0
- package/dist/detection/index.js.map +1 -0
- package/dist/errors/index.d.ts +69 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +110 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/console-logger.d.ts +12 -0
- package/dist/logging/console-logger.d.ts.map +1 -0
- package/dist/logging/console-logger.js +22 -0
- package/dist/logging/console-logger.js.map +1 -0
- package/dist/logging/index.d.ts +4 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.interface.d.ts +20 -0
- package/dist/logging/logger.interface.d.ts.map +1 -0
- package/dist/logging/logger.interface.js +2 -0
- package/dist/logging/logger.interface.js.map +1 -0
- package/dist/logging/silent-logger.d.ts +12 -0
- package/dist/logging/silent-logger.d.ts.map +1 -0
- package/dist/logging/silent-logger.js +21 -0
- package/dist/logging/silent-logger.js.map +1 -0
- package/dist/strategies/copy-contents.d.ts +14 -0
- package/dist/strategies/copy-contents.d.ts.map +1 -0
- package/dist/strategies/copy-contents.js +69 -0
- package/dist/strategies/copy-contents.js.map +1 -0
- package/dist/strategies/copy-overwrite.d.ts +14 -0
- package/dist/strategies/copy-overwrite.d.ts.map +1 -0
- package/dist/strategies/copy-overwrite.js +47 -0
- package/dist/strategies/copy-overwrite.js.map +1 -0
- package/dist/strategies/create-only.d.ts +13 -0
- package/dist/strategies/create-only.d.ts.map +1 -0
- package/dist/strategies/create-only.js +30 -0
- package/dist/strategies/create-only.js.map +1 -0
- package/dist/strategies/index.d.ts +31 -0
- package/dist/strategies/index.d.ts.map +1 -0
- package/dist/strategies/index.js +52 -0
- package/dist/strategies/index.js.map +1 -0
- package/dist/strategies/merge.d.ts +13 -0
- package/dist/strategies/merge.d.ts.map +1 -0
- package/dist/strategies/merge.js +60 -0
- package/dist/strategies/merge.js.map +1 -0
- package/dist/strategies/strategy.interface.d.ts +31 -0
- package/dist/strategies/strategy.interface.d.ts.map +1 -0
- package/dist/strategies/strategy.interface.js +2 -0
- package/dist/strategies/strategy.interface.js.map +1 -0
- package/dist/transaction/backup.d.ts +38 -0
- package/dist/transaction/backup.d.ts.map +1 -0
- package/dist/transaction/backup.js +97 -0
- package/dist/transaction/backup.js.map +1 -0
- package/dist/transaction/index.d.ts +4 -0
- package/dist/transaction/index.d.ts.map +1 -0
- package/dist/transaction/index.js +3 -0
- package/dist/transaction/index.js.map +1 -0
- package/dist/transaction/transaction.d.ts +34 -0
- package/dist/transaction/transaction.d.ts.map +1 -0
- package/dist/transaction/transaction.js +68 -0
- package/dist/transaction/transaction.js.map +1 -0
- package/dist/utils/file-operations.d.ts +29 -0
- package/dist/utils/file-operations.d.ts.map +1 -0
- package/dist/utils/file-operations.js +84 -0
- package/dist/utils/file-operations.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/json-utils.d.ts +22 -0
- package/dist/utils/json-utils.d.ts.map +1 -0
- package/dist/utils/json-utils.js +57 -0
- package/dist/utils/json-utils.js.map +1 -0
- package/dist/utils/path-utils.d.ts +21 -0
- package/dist/utils/path-utils.d.ts.map +1 -0
- package/dist/utils/path-utils.js +35 -0
- package/dist/utils/path-utils.js.map +1 -0
- package/eslint-plugin-code-organization/README.md +149 -0
- package/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
- package/eslint-plugin-code-organization/index.js +23 -0
- package/eslint-plugin-code-organization/package.json +10 -0
- package/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
- package/expo/copy-overwrite/.claude/skills/apollo-client/SKILL.md +238 -0
- package/expo/copy-overwrite/.claude/skills/apollo-client/references/mutation-patterns.md +360 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/SKILL.md +360 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/atomic-levels.md +417 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/folder-structure.md +257 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/gluestack-mapping.md +233 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +327 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/SKILL.md +299 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/examples.md +749 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/patterns.md +318 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/create_component.py +198 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/validate_component.py +207 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/SKILL.md +268 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/common-issues.md +619 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/file-extensions.md +340 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/platform-api.md +276 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/scripts/validate_cross_platform.py +414 -0
- package/expo/copy-overwrite/.claude/skills/directory-structure/SKILL.md +202 -0
- package/expo/copy-overwrite/.claude/skills/directory-structure/scripts/validate_structure.py +443 -0
- package/expo/copy-overwrite/.claude/skills/expo-env-config/SKILL.md +309 -0
- package/expo/copy-overwrite/.claude/skills/expo-env-config/references/validation-patterns.md +417 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/SKILL.md +431 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/references/official-docs.md +290 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/scripts/generate-route.py +169 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/SKILL.md +411 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/color-tokens.md +343 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/component-mapping.md +307 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/spacing-scale.md +300 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/scripts/validate_styling.py +354 -0
- package/expo/copy-overwrite/.claude/skills/local-state/SKILL.md +362 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/async-storage.md +505 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/persistence-patterns.md +711 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/reactive-variables.md +446 -0
- package/expo/copy-overwrite/.claude/skills/playwright-selectors/SKILL.md +223 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/SKILL.md +319 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/async-patterns.md +420 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/expo-router-testing.md +556 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/mocking-patterns.md +590 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/query-priority.md +291 -0
- package/expo/copy-overwrite/.easignore.extra +2 -0
- package/expo/copy-overwrite/.mcp.json +33 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/README.md +234 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/plugin-index.test.js +84 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/require-memo-in-view.test.js +196 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/single-component-per-file.test.js +289 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/index.js +32 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/package.json +10 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/enforce-component-structure.js +230 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/no-return-in-view.js +91 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/require-memo-in-view.js +178 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/single-component-per-file.js +238 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/README.md +260 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/index.js +29 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/package.json +10 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-classname-outside-ui.js +51 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-direct-rn-imports.js +55 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-inline-styles.js +73 -0
- package/expo/copy-overwrite/eslint.config.mjs +560 -0
- package/expo/copy-overwrite/lighthouserc.js +194 -0
- package/expo/create-only/lighthouserc-config.json +28 -0
- package/expo/merge/package.json +132 -0
- package/lisa.sh +35 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/SKILL.md +176 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/advanced-features.md +527 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/project-patterns.md +483 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/quick-start.md +257 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/resolvers-mutations.md +413 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/types-scalars.md +513 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-rules/SKILL.md +536 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/SKILL.md +275 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/configuration-patterns.md +487 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/entity-patterns.md +450 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/observability-patterns.md +536 -0
- package/nestjs/merge/package.json +75 -0
- package/package.json +124 -0
- package/typescript/copy-contents/.husky/commit-msg +91 -0
- package/typescript/copy-contents/.husky/pre-commit +96 -0
- package/typescript/copy-contents/.husky/pre-push +211 -0
- package/typescript/copy-overwrite/.claude/hooks/format-on-edit.sh +74 -0
- package/typescript/copy-overwrite/.claude/hooks/install_pkgs.sh +59 -0
- package/typescript/copy-overwrite/.claude/hooks/lint-on-edit.sh +103 -0
- package/typescript/copy-overwrite/.claude/skills/jsdoc-best-practices/SKILL.md +388 -0
- package/typescript/copy-overwrite/.github/README.md +455 -0
- package/typescript/copy-overwrite/.github/dependabot.yml +40 -0
- package/typescript/copy-overwrite/.github/k6/BROWSER_TESTING_NOTE.md +129 -0
- package/typescript/copy-overwrite/.github/k6/INTEGRATION_GUIDE.md +354 -0
- package/typescript/copy-overwrite/.github/k6/README.md +386 -0
- package/typescript/copy-overwrite/.github/k6/SCENARIO_SELECTION_GUIDE.md +264 -0
- package/typescript/copy-overwrite/.github/k6/examples/customer-deploy-integration.yml +115 -0
- package/typescript/copy-overwrite/.github/k6/examples/data-driven-test.js +268 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/load.js +142 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/load.json +27 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/smoke.js +26 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/smoke.json +20 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/soak.js +244 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/soak.json +29 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/spike.js +180 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/spike.json +32 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/stress.js +206 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/stress.json +38 -0
- package/typescript/copy-overwrite/.github/k6/scripts/api-test.js +452 -0
- package/typescript/copy-overwrite/.github/k6/scripts/default-test.js +185 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/normal.json +30 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/relaxed.json +21 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/strict.json +29 -0
- package/typescript/copy-overwrite/.github/workflows/build.yml +72 -0
- package/typescript/copy-overwrite/.github/workflows/ci.yml +49 -0
- package/typescript/copy-overwrite/.github/workflows/claude.yml +51 -0
- package/typescript/copy-overwrite/.github/workflows/create-github-issue-on-failure.yml +113 -0
- package/typescript/copy-overwrite/.github/workflows/create-jira-issue-on-failure.yml +195 -0
- package/typescript/copy-overwrite/.github/workflows/create-sentry-issue-on-failure.yml +267 -0
- package/typescript/copy-overwrite/.github/workflows/deploy.yml +228 -0
- package/typescript/copy-overwrite/.github/workflows/k6-load-test-README.md +230 -0
- package/typescript/copy-overwrite/.github/workflows/lighthouse.yml +68 -0
- package/typescript/copy-overwrite/.github/workflows/load-test.yml +282 -0
- package/typescript/copy-overwrite/.github/workflows/quality.yml +1737 -0
- package/typescript/copy-overwrite/.github/workflows/release.yml +1599 -0
- package/typescript/copy-overwrite/.gitleaksignore +28 -0
- package/typescript/copy-overwrite/.nvmrc +1 -0
- package/typescript/copy-overwrite/.prettierignore +23 -0
- package/typescript/copy-overwrite/.prettierrc.json +22 -0
- package/typescript/copy-overwrite/.versionrc +42 -0
- package/typescript/copy-overwrite/.yamllint +20 -0
- package/typescript/copy-overwrite/commitlint.config.js +11 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/README.md +149 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/index.js +23 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/package.json +10 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
- package/typescript/copy-overwrite/eslint.config.mjs +390 -0
- package/typescript/copy-overwrite/eslint.ignore.config.json +57 -0
- package/typescript/copy-overwrite/eslint.thresholds.config.json +5 -0
- package/typescript/github-rulesets/base.json +106 -0
- package/typescript/merge/.claude/settings.json +28 -0
- package/typescript/merge/package.json +71 -0
package/expo/copy-overwrite/eslint-plugin-component-structure/rules/single-component-per-file.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule to enforce exactly one React component per file
|
|
3
|
+
*
|
|
4
|
+
* This rule ensures that View and Container files contain only one React component.
|
|
5
|
+
* A React component is defined as any PascalCase function that returns JSX.
|
|
6
|
+
*
|
|
7
|
+
* Applies to:
|
|
8
|
+
* - *View.tsx, *View.jsx files
|
|
9
|
+
* - *Container.tsx, *Container.jsx files
|
|
10
|
+
*
|
|
11
|
+
* Excludes:
|
|
12
|
+
* - components/ui/** directory (third-party generated files)
|
|
13
|
+
* - components/custom/ui/** directory (third-party generated files)
|
|
14
|
+
* - components/shared/** directory (shared utility components)
|
|
15
|
+
* @module eslint-plugin-component-structure/rules/single-component-per-file
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
meta: {
|
|
20
|
+
type: "problem",
|
|
21
|
+
docs: {
|
|
22
|
+
description:
|
|
23
|
+
"Enforce exactly one React component per View or Container file",
|
|
24
|
+
category: "Best Practices",
|
|
25
|
+
recommended: true,
|
|
26
|
+
},
|
|
27
|
+
schema: [],
|
|
28
|
+
messages: {
|
|
29
|
+
multipleComponents:
|
|
30
|
+
"Only one React component is allowed per file. Found '{{componentName}}' in addition to '{{firstComponentName}}'. Extract '{{componentName}}' to a separate file.",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
create(context) {
|
|
35
|
+
const filename = context.getFilename();
|
|
36
|
+
const normalizedPath = filename.replace(/\\/g, "/");
|
|
37
|
+
|
|
38
|
+
// Only check View and Container files
|
|
39
|
+
const isViewOrContainer =
|
|
40
|
+
filename.endsWith("View.tsx") ||
|
|
41
|
+
filename.endsWith("View.jsx") ||
|
|
42
|
+
filename.endsWith("Container.tsx") ||
|
|
43
|
+
filename.endsWith("Container.jsx");
|
|
44
|
+
|
|
45
|
+
if (!isViewOrContainer) {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Exclude components/ui/**, components/custom/ui/**, and components/shared/** directories
|
|
50
|
+
if (
|
|
51
|
+
normalizedPath.includes("/components/ui/") ||
|
|
52
|
+
normalizedPath.includes("/components/custom/ui/") ||
|
|
53
|
+
normalizedPath.includes("/components/shared/") ||
|
|
54
|
+
normalizedPath.startsWith("components/ui/") ||
|
|
55
|
+
normalizedPath.startsWith("components/custom/ui/") ||
|
|
56
|
+
normalizedPath.startsWith("components/shared/")
|
|
57
|
+
) {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check if file is in features/**/components directory or components directory
|
|
62
|
+
const isFeatureComponent =
|
|
63
|
+
normalizedPath.includes("/features/") &&
|
|
64
|
+
normalizedPath.includes("/components/");
|
|
65
|
+
const isComponentsDir =
|
|
66
|
+
normalizedPath.includes("/components/") ||
|
|
67
|
+
normalizedPath.startsWith("components/");
|
|
68
|
+
|
|
69
|
+
if (!isFeatureComponent && !isComponentsDir) {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const state = {
|
|
74
|
+
components: [], // Array of { name, node }
|
|
75
|
+
firstComponent: null,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Recursively checks if an expression contains JSX
|
|
80
|
+
* @param {object} node - AST node to check
|
|
81
|
+
* @returns {boolean} True if expression contains JSX
|
|
82
|
+
*/
|
|
83
|
+
const containsJSX = node => {
|
|
84
|
+
if (!node) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const type = node.type;
|
|
89
|
+
if (type === "JSXElement" || type === "JSXFragment") {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check conditional expressions: condition ? consequent : alternate
|
|
94
|
+
if (type === "ConditionalExpression") {
|
|
95
|
+
return containsJSX(node.consequent) || containsJSX(node.alternate);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Check logical expressions: left && right, left || right
|
|
99
|
+
if (type === "LogicalExpression") {
|
|
100
|
+
return containsJSX(node.left) || containsJSX(node.right);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check parenthesized expressions
|
|
104
|
+
if (type === "ParenthesizedExpression") {
|
|
105
|
+
return containsJSX(node.expression);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return false;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Checks if a function returns JSX by examining its body
|
|
113
|
+
* @param {object} node - AST node to check
|
|
114
|
+
* @returns {boolean} True if function returns JSX
|
|
115
|
+
*/
|
|
116
|
+
const returnsJSX = node => {
|
|
117
|
+
if (!node || !node.body) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Handle arrow function with direct JSX return (no block)
|
|
122
|
+
if (node.type === "ArrowFunctionExpression") {
|
|
123
|
+
return containsJSX(node.body);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Handle function with block body
|
|
127
|
+
if (node.body.type === "BlockStatement") {
|
|
128
|
+
const hasJSXReturn = node.body.body.some(statement => {
|
|
129
|
+
return (
|
|
130
|
+
statement.type === "ReturnStatement" &&
|
|
131
|
+
containsJSX(statement.argument)
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (hasJSXReturn) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return false;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Records a component if it meets all criteria (PascalCase + returns JSX)
|
|
145
|
+
* @param {string} name - Component name
|
|
146
|
+
* @param {object} node - AST node
|
|
147
|
+
* @param {object} functionNode - Function AST node
|
|
148
|
+
*/
|
|
149
|
+
const recordComponent = (name, node, functionNode) => {
|
|
150
|
+
// Check if name is PascalCase
|
|
151
|
+
if (!/^[A-Z]/.test(name)) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check if function returns JSX
|
|
156
|
+
if (!returnsJSX(functionNode)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// This is a component - record it
|
|
161
|
+
if (state.components.length === 0) {
|
|
162
|
+
state.firstComponent = { name, node };
|
|
163
|
+
}
|
|
164
|
+
state.components.push({ name, node });
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
VariableDeclarator(node) {
|
|
169
|
+
if (node.id.type !== "Identifier") {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const name = node.id.name;
|
|
174
|
+
|
|
175
|
+
// Check for arrow function assignment: const Component = () => <div />
|
|
176
|
+
if (node.init && node.init.type === "ArrowFunctionExpression") {
|
|
177
|
+
const jsxCheck = returnsJSX(node.init);
|
|
178
|
+
if (jsxCheck) {
|
|
179
|
+
recordComponent(name, node, node.init);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Check for memo-wrapped component: const Component = memo(() => <div />)
|
|
185
|
+
if (
|
|
186
|
+
node.init &&
|
|
187
|
+
node.init.type === "CallExpression" &&
|
|
188
|
+
((node.init.callee.type === "Identifier" &&
|
|
189
|
+
node.init.callee.name === "memo") ||
|
|
190
|
+
(node.init.callee.type === "MemberExpression" &&
|
|
191
|
+
node.init.callee.object.name === "React" &&
|
|
192
|
+
node.init.callee.property.name === "memo"))
|
|
193
|
+
) {
|
|
194
|
+
const firstArg = node.init.arguments[0];
|
|
195
|
+
if (firstArg && returnsJSX(firstArg)) {
|
|
196
|
+
recordComponent(name, node, firstArg);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check for React.FC typed components: const Component: React.FC = () => <div />
|
|
202
|
+
if (
|
|
203
|
+
node.init &&
|
|
204
|
+
node.init.type === "ArrowFunctionExpression" &&
|
|
205
|
+
node.id.typeAnnotation &&
|
|
206
|
+
returnsJSX(node.init)
|
|
207
|
+
) {
|
|
208
|
+
recordComponent(name, node, node.init);
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
FunctionDeclaration(node) {
|
|
213
|
+
if (!node.id || node.id.type !== "Identifier") {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const name = node.id.name;
|
|
218
|
+
recordComponent(name, node, node);
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
"Program:exit"() {
|
|
222
|
+
// Report all components after the first one
|
|
223
|
+
if (state.components.length > 1) {
|
|
224
|
+
state.components.slice(1).forEach(component => {
|
|
225
|
+
context.report({
|
|
226
|
+
node: component.node,
|
|
227
|
+
messageId: "multipleComponents",
|
|
228
|
+
data: {
|
|
229
|
+
componentName: component.name,
|
|
230
|
+
firstComponentName: state.firstComponent.name,
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
},
|
|
238
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# ESLint Plugin: UI Standards
|
|
2
|
+
|
|
3
|
+
Custom ESLint rules for enforcing UI-related coding standards in React Native applications.
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
### no-classname-outside-ui
|
|
8
|
+
|
|
9
|
+
Restricts the use of `className` prop to designated UI component directories.
|
|
10
|
+
|
|
11
|
+
#### Rule Details
|
|
12
|
+
|
|
13
|
+
This rule ensures that `className` (used with Tailwind/NativeWind) is only used in reusable UI components. Business components should use semantic props instead of styling classes.
|
|
14
|
+
|
|
15
|
+
**Why this rule exists:**
|
|
16
|
+
- Keeps styling concerns in UI layer components
|
|
17
|
+
- Business components remain style-agnostic
|
|
18
|
+
- Makes component APIs more semantic and maintainable
|
|
19
|
+
- Facilitates design system consistency
|
|
20
|
+
|
|
21
|
+
**Where is className allowed?**
|
|
22
|
+
- `components/ui/` - Core UI components
|
|
23
|
+
- `components/custom/ui/` - Custom UI components
|
|
24
|
+
|
|
25
|
+
#### Examples
|
|
26
|
+
|
|
27
|
+
**Incorrect** (className in business component):
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
// features/user/components/ProfileCard/ProfileCardView.tsx
|
|
31
|
+
const ProfileCardView = ({ user }) => (
|
|
32
|
+
<View className="p-4 bg-white rounded-lg"> {/* className here - NOT allowed */}
|
|
33
|
+
<Text className="text-lg font-bold">{user.name}</Text>
|
|
34
|
+
</View>
|
|
35
|
+
);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Correct** (using UI components with semantic props):
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// features/user/components/ProfileCard/ProfileCardView.tsx
|
|
42
|
+
import { Card, Heading } from '@/components/ui';
|
|
43
|
+
|
|
44
|
+
const ProfileCardView = ({ user }) => (
|
|
45
|
+
<Card variant="elevated">
|
|
46
|
+
<Heading size="lg">{user.name}</Heading>
|
|
47
|
+
</Card>
|
|
48
|
+
);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Correct** (className in UI component):
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
// components/ui/Card/CardView.tsx
|
|
55
|
+
const CardView = ({ variant, children }) => (
|
|
56
|
+
<View className={cn("rounded-lg", variants[variant])}>
|
|
57
|
+
{children}
|
|
58
|
+
</View>
|
|
59
|
+
);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
#### Configuration
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
// eslint.config.mjs
|
|
66
|
+
{
|
|
67
|
+
rules: {
|
|
68
|
+
'ui-standards/no-classname-outside-ui': ['error', {
|
|
69
|
+
allowedPaths: ['/components/ui/', '/components/custom/ui/']
|
|
70
|
+
}]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Options:**
|
|
76
|
+
|
|
77
|
+
| Option | Type | Default | Description |
|
|
78
|
+
|--------|------|---------|-------------|
|
|
79
|
+
| `allowedPaths` | `string[]` | `['/components/ui/', '/components/custom/ui/']` | Paths where className is allowed |
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
### no-direct-rn-imports
|
|
84
|
+
|
|
85
|
+
Prevents direct imports from `react-native` to encourage use of wrapped UI components.
|
|
86
|
+
|
|
87
|
+
#### Rule Details
|
|
88
|
+
|
|
89
|
+
This rule blocks direct imports from `react-native` in favor of using the project's UI component library. This ensures:
|
|
90
|
+
|
|
91
|
+
- Consistent styling across the app
|
|
92
|
+
- Ability to swap underlying implementations
|
|
93
|
+
- Centralized accessibility handling
|
|
94
|
+
- Design system compliance
|
|
95
|
+
|
|
96
|
+
**What's blocked?**
|
|
97
|
+
- `import { View, Text, ... } from 'react-native'`
|
|
98
|
+
|
|
99
|
+
**What to use instead?**
|
|
100
|
+
- `import { View, Text, ... } from '@/components/ui'`
|
|
101
|
+
|
|
102
|
+
#### Examples
|
|
103
|
+
|
|
104
|
+
**Incorrect:**
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
import { View, Text, TouchableOpacity } from 'react-native';
|
|
108
|
+
|
|
109
|
+
const MyComponent = () => (
|
|
110
|
+
<View>
|
|
111
|
+
<Text>Hello</Text>
|
|
112
|
+
<TouchableOpacity onPress={handlePress}>
|
|
113
|
+
<Text>Click me</Text>
|
|
114
|
+
</TouchableOpacity>
|
|
115
|
+
</View>
|
|
116
|
+
);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Correct:**
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { View, Text, Button } from '@/components/ui';
|
|
123
|
+
|
|
124
|
+
const MyComponent = () => (
|
|
125
|
+
<View>
|
|
126
|
+
<Text>Hello</Text>
|
|
127
|
+
<Button onPress={handlePress}>Click me</Button>
|
|
128
|
+
</View>
|
|
129
|
+
);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### Configuration
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
// eslint.config.mjs
|
|
136
|
+
{
|
|
137
|
+
rules: {
|
|
138
|
+
'ui-standards/no-direct-rn-imports': 'error'
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Allowed directories:**
|
|
144
|
+
- `components/ui/` - UI wrappers need to import from react-native
|
|
145
|
+
- `components/custom/ui/` - Custom UI components
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### no-inline-styles
|
|
150
|
+
|
|
151
|
+
Prevents inline style objects in favor of className-based styling.
|
|
152
|
+
|
|
153
|
+
#### Rule Details
|
|
154
|
+
|
|
155
|
+
This rule prohibits inline `style` props with object literals. All styling should use className with Tailwind/NativeWind classes.
|
|
156
|
+
|
|
157
|
+
**Why this rule exists:**
|
|
158
|
+
- Inline styles bypass the design system
|
|
159
|
+
- Harder to maintain consistency
|
|
160
|
+
- NativeWind provides better performance
|
|
161
|
+
- Enables theme support
|
|
162
|
+
|
|
163
|
+
#### Examples
|
|
164
|
+
|
|
165
|
+
**Incorrect:**
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
const MyView = () => (
|
|
169
|
+
<View style={{ padding: 16, backgroundColor: 'white' }}>
|
|
170
|
+
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>Title</Text>
|
|
171
|
+
</View>
|
|
172
|
+
);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Incorrect** (StyleSheet is also discouraged):
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
const styles = StyleSheet.create({
|
|
179
|
+
container: { padding: 16 }
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const MyView = () => (
|
|
183
|
+
<View style={styles.container}>
|
|
184
|
+
<Text>Content</Text>
|
|
185
|
+
</View>
|
|
186
|
+
);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Correct:**
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
const MyView = () => (
|
|
193
|
+
<View className="p-4 bg-white">
|
|
194
|
+
<Text className="text-lg font-bold">Title</Text>
|
|
195
|
+
</View>
|
|
196
|
+
);
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### Configuration
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
// eslint.config.mjs
|
|
203
|
+
{
|
|
204
|
+
rules: {
|
|
205
|
+
'ui-standards/no-inline-styles': 'error'
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Allowed exceptions:**
|
|
211
|
+
- Dynamic styles computed at runtime (with eslint-disable comment)
|
|
212
|
+
- Animation styles (Reanimated)
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Installation
|
|
217
|
+
|
|
218
|
+
This plugin is installed locally as a file dependency:
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"devDependencies": {
|
|
223
|
+
"eslint-plugin-ui-standards": "file:./eslint-plugin-ui-standards"
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Usage with ESLint 9 Flat Config
|
|
229
|
+
|
|
230
|
+
```javascript
|
|
231
|
+
// eslint.config.mjs
|
|
232
|
+
import uiStandardsPlugin from './eslint-plugin-ui-standards/index.js';
|
|
233
|
+
|
|
234
|
+
export default [
|
|
235
|
+
{
|
|
236
|
+
plugins: {
|
|
237
|
+
'ui-standards': uiStandardsPlugin,
|
|
238
|
+
},
|
|
239
|
+
rules: {
|
|
240
|
+
'ui-standards/no-classname-outside-ui': 'error',
|
|
241
|
+
'ui-standards/no-direct-rn-imports': 'error',
|
|
242
|
+
'ui-standards/no-inline-styles': 'error',
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
];
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Contributing
|
|
249
|
+
|
|
250
|
+
When adding new rules:
|
|
251
|
+
|
|
252
|
+
1. Create rule implementation in `rules/`
|
|
253
|
+
2. Add tests in `__tests__/`
|
|
254
|
+
3. Export in `index.js`
|
|
255
|
+
4. Document in this README
|
|
256
|
+
5. Add to ESLint configuration
|
|
257
|
+
|
|
258
|
+
## Version
|
|
259
|
+
|
|
260
|
+
1.0.0
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint plugin for UI standards
|
|
3
|
+
*
|
|
4
|
+
* This plugin enforces UI-related coding standards for React Native components.
|
|
5
|
+
* Supports ESLint 9 flat config format.
|
|
6
|
+
*
|
|
7
|
+
* Rules:
|
|
8
|
+
* - no-classname-outside-ui: Disallows className prop outside UI components
|
|
9
|
+
* - no-direct-rn-imports: Disallows direct React Native imports
|
|
10
|
+
* - no-inline-styles: Disallows inline style objects
|
|
11
|
+
* @module eslint-plugin-ui-standards
|
|
12
|
+
*/
|
|
13
|
+
const noClassnameOutsideUi = require("./rules/no-classname-outside-ui");
|
|
14
|
+
const noDirectRnImports = require("./rules/no-direct-rn-imports");
|
|
15
|
+
const noInlineStyles = require("./rules/no-inline-styles");
|
|
16
|
+
|
|
17
|
+
const plugin = {
|
|
18
|
+
meta: {
|
|
19
|
+
name: "eslint-plugin-ui-standards",
|
|
20
|
+
version: "1.0.0",
|
|
21
|
+
},
|
|
22
|
+
rules: {
|
|
23
|
+
"no-classname-outside-ui": noClassnameOutsideUi,
|
|
24
|
+
"no-direct-rn-imports": noDirectRnImports,
|
|
25
|
+
"no-inline-styles": noInlineStyles,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
module.exports = plugin;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "problem",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "Disallow className outside of UI component directories",
|
|
6
|
+
category: "Best Practices",
|
|
7
|
+
recommended: true,
|
|
8
|
+
},
|
|
9
|
+
fixable: null,
|
|
10
|
+
schema: [
|
|
11
|
+
{
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
allowedPaths: {
|
|
15
|
+
type: "array",
|
|
16
|
+
items: { type: "string" },
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
additionalProperties: false,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
messages: {
|
|
23
|
+
noClassNameOutsideUI:
|
|
24
|
+
"className is only allowed in components/ui and components/custom/ui directories. Create a reusable component with semantic props instead.",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
create(context) {
|
|
29
|
+
const options = context.options[0] || {};
|
|
30
|
+
const allowedPaths = options.allowedPaths || [
|
|
31
|
+
"/components/ui/",
|
|
32
|
+
"/components/custom/ui/",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
JSXAttribute(node) {
|
|
37
|
+
if (node.name.name !== "className") return;
|
|
38
|
+
|
|
39
|
+
const filename = context.getFilename().replace(/\\/g, "/");
|
|
40
|
+
const isAllowed = allowedPaths.some(path => filename.includes(path));
|
|
41
|
+
|
|
42
|
+
if (!isAllowed) {
|
|
43
|
+
context.report({
|
|
44
|
+
node,
|
|
45
|
+
messageId: "noClassNameOutsideUI",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "problem",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "Prevent direct React Native component imports",
|
|
6
|
+
category: "Best Practices",
|
|
7
|
+
recommended: true,
|
|
8
|
+
},
|
|
9
|
+
fixable: null,
|
|
10
|
+
schema: [],
|
|
11
|
+
messages: {
|
|
12
|
+
noDirectRNImport:
|
|
13
|
+
"Don't import {{importName}} from 'react-native'. Use {{suggestion}} from '@/components/ui' instead.",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
create(context) {
|
|
18
|
+
const componentMap = {
|
|
19
|
+
View: "Box",
|
|
20
|
+
Text: "Text",
|
|
21
|
+
Image: "Image",
|
|
22
|
+
ScrollView: "ScrollView",
|
|
23
|
+
Pressable: "Pressable",
|
|
24
|
+
TouchableOpacity: "Pressable",
|
|
25
|
+
TouchableHighlight: "Pressable",
|
|
26
|
+
TouchableWithoutFeedback: "Pressable",
|
|
27
|
+
TextInput: "Input",
|
|
28
|
+
FlatList: "FlatList",
|
|
29
|
+
SectionList: "SectionList",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
ImportDeclaration(node) {
|
|
34
|
+
if (node.source.value !== "react-native") return;
|
|
35
|
+
|
|
36
|
+
node.specifiers.forEach(specifier => {
|
|
37
|
+
if (specifier.type === "ImportSpecifier") {
|
|
38
|
+
const importedName = specifier.imported.name;
|
|
39
|
+
|
|
40
|
+
if (componentMap[importedName]) {
|
|
41
|
+
context.report({
|
|
42
|
+
node: specifier,
|
|
43
|
+
messageId: "noDirectRNImport",
|
|
44
|
+
data: {
|
|
45
|
+
importName: importedName,
|
|
46
|
+
suggestion: componentMap[importedName],
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
};
|