├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md └── workflows │ ├── ci.yml │ └── version.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmrc ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── eslint.config.mjs ├── package.json ├── packages ├── eslint-plugin-solid │ ├── README.md │ ├── docs │ │ ├── components-return-once.md │ │ ├── event-handlers.md │ │ ├── imports.md │ │ ├── jsx-no-duplicate-props.md │ │ ├── jsx-no-script-url.md │ │ ├── jsx-no-undef.md │ │ ├── jsx-uses-vars.md │ │ ├── no-array-handlers.md │ │ ├── no-destructure.md │ │ ├── no-innerhtml.md │ │ ├── no-proxy-apis.md │ │ ├── no-react-deps.md │ │ ├── no-react-specific-props.md │ │ ├── no-unknown-namespaces.md │ │ ├── prefer-classlist.md │ │ ├── prefer-for.md │ │ ├── prefer-show.md │ │ ├── reactivity.md │ │ ├── self-closing-comp.md │ │ └── style-prop.md │ ├── package.json │ ├── scripts │ │ └── docs.ts │ ├── src │ │ ├── compat.ts │ │ ├── configs │ │ │ ├── recommended.ts │ │ │ └── typescript.ts │ │ ├── deps.d.ts │ │ ├── index.ts │ │ ├── plugin.ts │ │ ├── rules │ │ │ ├── components-return-once.ts │ │ │ ├── event-handlers.ts │ │ │ ├── imports.ts │ │ │ ├── jsx-no-duplicate-props.ts │ │ │ ├── jsx-no-script-url.ts │ │ │ ├── jsx-no-undef.ts │ │ │ ├── jsx-uses-vars.ts │ │ │ ├── no-array-handlers.ts │ │ │ ├── no-destructure.ts │ │ │ ├── no-innerhtml.ts │ │ │ ├── no-proxy-apis.ts │ │ │ ├── no-react-deps.ts │ │ │ ├── no-react-specific-props.ts │ │ │ ├── no-unknown-namespaces.ts │ │ │ ├── prefer-classlist.ts │ │ │ ├── prefer-for.ts │ │ │ ├── prefer-show.ts │ │ │ ├── reactivity.ts │ │ │ ├── self-closing-comp.ts │ │ │ ├── style-prop.ts │ │ │ └── validate-jsx-nesting.ts │ │ └── utils.ts │ ├── test │ │ ├── ruleTester.ts │ │ └── rules │ │ │ ├── components-return-once.test.ts │ │ │ ├── event-handlers.test.ts │ │ │ ├── imports.test.ts │ │ │ ├── jsx-no-duplicate-props.test.ts │ │ │ ├── jsx-no-script-url.test.ts │ │ │ ├── jsx-no-undef.test.ts │ │ │ ├── jsx-uses-vars.test.ts │ │ │ ├── no-array-handlers.test.ts │ │ │ ├── no-destructure.test.ts │ │ │ ├── no-innerhtml.test.ts │ │ │ ├── no-proxy-apis.test.ts │ │ │ ├── no-react-deps.test.ts │ │ │ ├── no-react-specific-props.test.ts │ │ │ ├── no-unknown-namespaces.test.ts │ │ │ ├── prefer-classlist.test.ts │ │ │ ├── prefer-for.test.ts │ │ │ ├── prefer-show.test.ts │ │ │ ├── reactivity.test.ts │ │ │ ├── self-closing-comp.test.ts │ │ │ └── style-prop.test.ts │ ├── tsup.config.ts │ ├── vitest.config.js │ └── vitest.setup.js └── eslint-solid-standalone │ ├── README.md │ ├── dist.d.ts │ ├── index.js │ ├── mock │ ├── assert.js │ ├── empty.js │ ├── glob-parent.js │ ├── globby.js │ ├── is-glob.js │ ├── path.js │ ├── semver.js │ └── util.js │ ├── package.json │ ├── pnpm-lock.yaml │ ├── rollup-plugin-replace.mjs │ ├── rollup.config.mjs │ └── test.mjs ├── patches └── eslint@8.57.0.patch ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts └── version.js ├── test ├── README.md ├── eslint.config.js ├── eslint.config.prefixed.js ├── fixture.test.ts ├── format.test.ts ├── invalid │ ├── jsx-undef.jsx │ └── reactivity-renaming.jsx ├── package.json ├── valid │ ├── async │ │ ├── lazy-components │ │ │ ├── greeting.tsx │ │ │ └── main.tsx │ │ ├── resources │ │ │ └── main.tsx │ │ ├── suspense-list │ │ │ ├── main.tsx │ │ │ ├── mock-api.tsx │ │ │ └── profile.tsx │ │ ├── suspense │ │ │ ├── greeting.tsx │ │ │ └── main.tsx │ │ └── transitions │ │ │ ├── child.tsx │ │ │ └── main.tsx │ ├── bindings │ │ ├── classlist │ │ │ └── main.tsx │ │ ├── directives │ │ │ ├── click-outside.tsx │ │ │ └── main.tsx │ │ ├── events │ │ │ └── main.tsx │ │ ├── forwarding-refs │ │ │ └── main.tsx │ │ ├── refs │ │ │ └── main.tsx │ │ ├── spreads │ │ │ └── main.tsx │ │ └── style │ │ │ └── main.tsx │ ├── control-flow │ │ ├── dynamic │ │ │ └── main.tsx │ │ ├── error-boundary │ │ │ └── main.tsx │ │ ├── for │ │ │ └── main.tsx │ │ ├── index │ │ │ └── main.tsx │ │ ├── portal │ │ │ └── main.tsx │ │ ├── show │ │ │ └── main.tsx │ │ └── switch │ │ │ └── main.tsx │ ├── examples │ │ ├── async-resource.tsx │ │ ├── counter.jsx │ │ ├── css-animations.jsx │ │ ├── familiar-and-modern.tsx │ │ ├── formvalidation-main.tsx │ │ ├── formvalidation-validation.tsx │ │ ├── introduction-signals.tsx │ │ ├── scoreboard.tsx │ │ ├── simple-todos.tsx │ │ ├── suspense-transitions-child.tsx │ │ └── suspense-transitions-main.tsx │ ├── introduction │ │ ├── basics │ │ │ └── main.tsx │ │ ├── components │ │ │ ├── main.tsx │ │ │ └── nested.tsx │ │ ├── derived-signals │ │ │ └── main.tsx │ │ ├── effects │ │ │ └── main.tsx │ │ ├── jsx │ │ │ └── main.tsx │ │ ├── memos │ │ │ └── main.tsx │ │ └── signals │ │ │ └── main.tsx │ ├── lifecycles │ │ ├── onCleanup │ │ │ └── main.tsx │ │ └── onMount │ │ │ └── main.tsx │ ├── props │ │ ├── children │ │ │ ├── colored-list.tsx │ │ │ └── main.tsx │ │ ├── default-props │ │ │ ├── greeting.tsx │ │ │ └── main.tsx │ │ └── splitting-props │ │ │ ├── greeting.tsx │ │ │ └── main.tsx │ ├── reactivity │ │ ├── batching-updates │ │ │ └── main.tsx │ │ ├── on │ │ │ └── main.tsx │ │ └── untrack │ │ │ └── main.tsx │ └── stores │ │ ├── context │ │ ├── counter.tsx │ │ ├── main.tsx │ │ └── nested.tsx │ │ ├── create-store │ │ └── main.tsx │ │ ├── immutable-stores │ │ ├── actions.tsx │ │ ├── main.tsx │ │ ├── store.tsx │ │ └── useRedux.tsx │ │ ├── mutation │ │ └── main.tsx │ │ ├── nested-reactivity │ │ └── main.tsx │ │ └── without-context │ │ ├── counter.tsx │ │ └── main.tsx └── vitest.config.js ├── tsconfig.json └── turbo.json /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Tell us what's wrong with the plugin 4 | title: "" 5 | labels: "bug" 6 | assignees: joshwilsonvu 7 | --- 8 | 9 | **Describe the bug** 10 | 11 | 12 | **To Reproduce** 13 | 14 | 15 | 16 | **Expected behavior** 17 | 18 | 19 | **Screenshots** 20 | 21 | 22 | **Environment (please complete the following information):** 23 | 24 | - OS: [e.g. Mac OS 11, Windows 10] 25 | - Node version (`node --version`): 26 | - `eslint-plugin-solid` version (`npm list eslint-plugin-solid`/`yarn why eslint-plugin-solid`): 27 | - `eslint` version (`npm list eslint`/`yarn why eslint`): 28 | 29 | **Additional context** 30 | 31 | 32 | 33 | - [ ] I would be willing to contribute a PR to fix this issue -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Tell us about a rule, option, or other feature you'd like to see added to the plugin 4 | title: "" 5 | labels: "enhancement" 6 | assignees: joshwilsonvu 7 | --- 8 | 9 | **Describe the need** 10 | 11 | 12 | **Suggested Solution** 13 | 14 | 15 | **Possible Alternatives** 16 | 17 | 18 | **Additional context** 19 | 20 | 21 | 22 | - [ ] I would be willing to contribute a PR to implement this feature -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question not related to a particular bug or feature request 4 | title: "" 5 | labels: "question" 6 | assignees: joshwilsonvu 7 | --- 8 | 9 | **Your Question** 10 | 11 | 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: ["main"] 5 | pull_request: 6 | types: [opened, synchronize] 7 | jobs: 8 | build: 9 | name: Build 10 | runs-on: ubuntu-latest 11 | permissions: 12 | id-token: write 13 | strategy: 14 | matrix: 15 | node: ["18", "20", "22"] 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | 20 | - name: Setup pnpm 21 | uses: pnpm/action-setup@v4 22 | with: 23 | run_install: false 24 | 25 | - name: Install node 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: ${{ matrix.node }} 29 | cache: "pnpm" 30 | registry-url: https://registry.npmjs.org/ 31 | 32 | - name: Install dependencies 33 | run: pnpm install --frozen-lockfile 34 | 35 | - name: Cache turbo setup 36 | uses: actions/cache@v4 37 | with: 38 | path: .turbo 39 | key: ${{ runner.os }}-turbo-${{ github.sha }} 40 | restore-keys: | 41 | ${{ runner.os }}-turbo- 42 | 43 | - name: Run CI with turbo 44 | run: pnpm run ci 45 | 46 | - name: Publish to npm if needed 47 | if: github.ref == 'refs/heads/main' && matrix.node == '22' 48 | env: 49 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 50 | run: pnpm publish -r --no-git-checks 51 | -------------------------------------------------------------------------------- /.github/workflows/version.yml: -------------------------------------------------------------------------------- 1 | name: Version 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: "Semver version bump" 7 | required: true 8 | type: choice 9 | options: [patch, minor, major] 10 | jobs: 11 | version: 12 | name: Version 13 | runs-on: ubuntu-latest 14 | if: github.ref == 'refs/heads/main' 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup pnpm 20 | uses: pnpm/action-setup@v4 21 | with: 22 | run_install: false 23 | 24 | - name: Install node 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: "22" 28 | cache: "pnpm" 29 | 30 | - name: Install dependencies 31 | run: pnpm install --frozen-lockfile 32 | 33 | - name: Bump version 34 | run: | 35 | git config user.name github-actions[bot] 36 | git config user.email github-actions[bot]@users.noreply.github.com 37 | pnpm run version ${{ inputs.version }} 38 | 39 | - name: Create PR with new versions 40 | uses: peter-evans/create-pull-request@v6 41 | with: 42 | branch: "gh-action-version" 43 | base: main 44 | delete-branch: true 45 | title: "Update package versions" 46 | body: "Merging this PR will publish packages to npm at the new version." 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build directory 2 | dist 3 | dist.* 4 | !packages/eslint-solid-standalone/dist.d.ts 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | .env.test 79 | 80 | # parcel-bundler cache (https://parceljs.org/) 81 | .cache 82 | 83 | # Next.js build output 84 | .next 85 | 86 | # Nuxt.js build / generate output 87 | .nuxt 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | .DS_Store 110 | 111 | # Turbo cache 112 | .turbo 113 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | pnpm run docs 5 | git add README.md packages/eslint-plugin-solid/README.md packages/eslint-plugin-solid/docs/* 6 | pnpm lint-staged 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | link-workspace-packages=true 2 | disallow-workspace-cycles=true 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `eslint-plugin-solid` 2 | 3 | Thanks for your interest in improving this project! We welcome questions, bug reports, and feature 4 | requests. Please file an issue before submitting a PR, and fill out applicable details in the chosen 5 | issue template. 6 | 7 | > Please see our [Code of Conduct](./CODE_OF_CONDUCT.md) before contributing. 8 | 9 | ## Development 10 | 11 | This project uses `pnpm` for package management. Run `pnpm i` to install dependencies after cloning 12 | the repo. 13 | 14 | To type-check and build the code, run `pnpm build`. This will also regenerate the docs from source 15 | code descriptions and test cases, using `scripts/docs.ts`. 16 | 17 | `pnpm lint` runs ESLint on the repo (so meta!). 18 | 19 | Testing is _extremely_ important to maintaining the quality of this project, so we require 20 | comprehensive tests for every rule, and we check against example code provided in the docs. To run 21 | tests for individual rules as well as integration/e2e tests, run `pnpm test`. To run tests for a 22 | specific rule like `reactivity`, run `pnpm test reactivity` or `pnpm test reactivity --watch`. 23 | Before releasing new versions, we run tests against various ESLint parsers with `pnpm test:all`. 24 | 25 | ### Adding a new rule 26 | 27 | For each rule, there's a few things to do for it to be ready for release. Let's say you have 28 | received approval to add a rule named `solid/some-name` in an issue: 29 | 30 | 1. Create `src/rules/some-name.ts`. Add the necessary imports and a default export of the form `{ 31 | meta: { ... }, create() { ... } }`. 32 | [`solid/no-react-specific-props`](./src/rules/no-react-specific-props.ts) is a good, brief 33 | example of what's necessary. 34 | 2. Create `test/rules/some-name.test.ts`. Add `valid` and `invalid` test cases, using other files 35 | for inspiration. Be sure to `export const cases` so `scripts/docs.ts` can pick up the test cases. 36 | 3. Create `docs/rules/some-name.md`. You can copy the content of 37 | `docs/rules/no-react-specific-props.md` directly, as all of its content is auto-generated. Run 38 | `pnpm build` and then verify that the content has been updated to reflect the new rule. 39 | 4. When good tests are written and passing, open `src/index.ts` and import your new rule. Add it to 40 | `allRules` and the `recommended` and `typescript` configs as appropriate. 41 | 5. Submit your PR and await feedback. When any necessary changes have been made, it will be merged. 42 | Congratulations! 43 | 44 | ## Publishing 45 | 46 | Publishing is currently done manually by @joshwilsonvu. When publishing a new version of 47 | `eslint-plugin-solid`, we also publish a corresponding version of `eslint-plugin-standalone`, which 48 | is a package primarily intended to support linting on https://playground.solidjs.com. 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Josh Wilson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import path from "node:path"; 3 | import js from "@eslint/js"; 4 | import globals from "globals"; 5 | import tseslint from "typescript-eslint"; 6 | import pluginEslintPlugin from "eslint-plugin-eslint-plugin"; 7 | 8 | const tsconfigPath = path.resolve("tsconfig.json"); 9 | 10 | export default tseslint.config( 11 | { 12 | ignores: ["**/dist/", "**/dist.*", "**/.tsup/", "**/eslint.config.mjs", "test/"], 13 | }, 14 | js.configs.recommended, 15 | tseslint.configs.eslintRecommended, 16 | ...tseslint.configs.recommended, 17 | { 18 | languageOptions: { 19 | sourceType: "module", 20 | parser: tseslint.parser, 21 | parserOptions: { 22 | project: tsconfigPath, 23 | }, 24 | globals: globals.node, 25 | }, 26 | rules: { 27 | "@typescript-eslint/no-explicit-any": "off", 28 | "@typescript-eslint/no-non-null-assertion": "off", 29 | "@typescript-eslint/non-nullable-type-assertion-style": "warn", 30 | "no-extra-semi": "off", 31 | "no-mixed-spaces-and-tabs": "off", 32 | "no-new-native-nonconstructor": 1, 33 | "no-new-symbol": "off", 34 | "object-shorthand": "warn", 35 | }, 36 | }, 37 | { 38 | files: ["packages/eslint-plugin-solid/src/rules/*.ts"], 39 | languageOptions: { 40 | globals: globals.node, 41 | }, 42 | plugins: { 43 | "eslint-plugin": pluginEslintPlugin, 44 | }, 45 | rules: { 46 | ...pluginEslintPlugin.configs.recommended.rules, 47 | "eslint-plugin/meta-property-ordering": "error", 48 | "eslint-plugin/report-message-format": ["error", "^[A-Z\\{'].*\\.$"], 49 | "eslint-plugin/test-case-property-ordering": "error", 50 | "eslint-plugin/require-meta-docs-description": [ 51 | "error", 52 | { pattern: "^(Enforce|Require|Disallow)" }, 53 | ], 54 | "eslint-plugin/require-meta-docs-url": [ 55 | "error", 56 | { 57 | pattern: 58 | "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/{{name}}.md", 59 | }, 60 | ], 61 | }, 62 | } 63 | ); 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-solid-monorepo", 3 | "private": true, 4 | "license": "MIT", 5 | "type": "module", 6 | "workspaces": [ 7 | "packages/*" 8 | ], 9 | "scripts": { 10 | "build": "turbo run turbo:build", 11 | "ci": "PARSER=all turbo run turbo:build turbo:test turbo:docs turbo:lint turbo:tsc", 12 | "docs": "turbo run turbo:docs", 13 | "lint": "turbo run turbo:lint", 14 | "prepare": "husky install", 15 | "test": "turbo run turbo:test", 16 | "tsc": "turbo run turbo:tsc", 17 | "turbo:docs": "cp packages/eslint-plugin-solid/README.md README.md", 18 | "turbo:lint": "eslint --max-warnings=0", 19 | "turbo:tsc": "tsc", 20 | "version": "node scripts/version.js" 21 | }, 22 | "lint-staged": { 23 | "*.{js,jsx,ts,tsx}": [ 24 | "pnpm lint -- --no-warn-ignored --fix", 25 | "prettier --write" 26 | ] 27 | }, 28 | "prettier": { 29 | "plugins": [ 30 | "prettier-plugin-packagejson" 31 | ], 32 | "printWidth": 100 33 | }, 34 | "devDependencies": { 35 | "@tsconfig/node20": "^20.1.4", 36 | "@types/node": "^20", 37 | "@types/prettier": "^2.7.3", 38 | "eslint": "^9.5.0", 39 | "eslint-plugin-eslint-plugin": "^6.1.0", 40 | "globals": "^15.6.0", 41 | "husky": "^8.0.3", 42 | "lint-staged": "^13.3.0", 43 | "prettier": "^2.8.8", 44 | "prettier-plugin-packagejson": "^2.5.1", 45 | "semver": "^7.6.0", 46 | "turbo": "^2.0.14", 47 | "typescript": "^5.5.4", 48 | "typescript-eslint": "^8.1.0" 49 | }, 50 | "packageManager": "pnpm@9.7.1", 51 | "pnpm": { 52 | "patchedDependencies": { 53 | "eslint@8.57.0": "patches/eslint@8.57.0.patch" 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/docs/imports.md: -------------------------------------------------------------------------------- 1 | 2 | # solid/imports 3 | Enforce consistent imports from "solid-js", "solid-js/web", and "solid-js/store". 4 | This rule is **a warning** by default. 5 | 6 | [View source](../src/rules/imports.ts) · [View tests](../test/rules/imports.test.ts) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ## Tests 15 | 16 | ### Invalid Examples 17 | 18 | These snippets cause lint errors, and all of them can be auto-fixed. 19 | 20 | ```js 21 | import { createEffect } from "solid-js/web"; 22 | // after eslint --fix: 23 | import { createEffect } from "solid-js"; 24 | 25 | import { createEffect } from "solid-js/web"; 26 | import { createSignal } from "solid-js"; 27 | // after eslint --fix: 28 | import { createSignal, createEffect } from "solid-js"; 29 | 30 | import type { Component } from "solid-js/store"; 31 | import { createSignal } from "solid-js"; 32 | console.log("hi"); 33 | // after eslint --fix: 34 | import { createSignal, Component } from "solid-js"; 35 | console.log("hi"); 36 | 37 | import { createSignal } from "solid-js/web"; 38 | import "solid-js"; 39 | // after eslint --fix: 40 | import { createSignal } from "solid-js"; 41 | 42 | import { createSignal } from "solid-js/web"; 43 | import {} from "solid-js"; 44 | // after eslint --fix: 45 | import { createSignal } from "solid-js"; 46 | 47 | import { createEffect } from "solid-js/web"; 48 | import { render } from "solid-js"; 49 | // after eslint --fix: 50 | import { render, createEffect } from "solid-js"; 51 | 52 | import { render, createEffect } from "solid-js"; 53 | // after eslint --fix: 54 | import { render } from "solid-js/web"; 55 | import { createEffect } from "solid-js"; 56 | ``` 57 | 58 | ### Valid Examples 59 | 60 | These snippets don't cause lint errors. 61 | 62 | ```js 63 | import { createSignal, mergeProps as merge } from "solid-js"; 64 | 65 | import { createSignal, mergeProps as merge } from "solid-js"; 66 | 67 | import { render, hydrate } from "solid-js/web"; 68 | 69 | import { createStore, produce } from "solid-js/store"; 70 | 71 | import { createSignal } from "solid-js"; 72 | import { render } from "solid-js/web"; 73 | import { something } from "somewhere/else"; 74 | import { createStore } from "solid-js/store"; 75 | 76 | import * as Solid from "solid-js"; 77 | Solid.render(); 78 | 79 | import type { Component, JSX } from "solid-js"; 80 | import type { Store } from "solid-js/store"; 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/docs/jsx-no-duplicate-props.md: -------------------------------------------------------------------------------- 1 | 2 | # solid/jsx-no-duplicate-props 3 | Disallow passing the same prop twice in JSX. 4 | This rule is **an error** by default. 5 | 6 | [View source](../src/rules/jsx-no-duplicate-props.ts) · [View tests](../test/rules/jsx-no-duplicate-props.test.ts) 7 | 8 | 9 | 10 | ## Rule Options 11 | 12 | Options shown here are the defaults. 13 | 14 | ```js 15 | { 16 | "solid/jsx-no-duplicate-props": ["error", { 17 | // Consider two prop names differing only by case to be the same. 18 | ignoreCase: false, 19 | }] 20 | } 21 | ``` 22 | 23 | 24 | 25 | ## Tests 26 | 27 | ### Invalid Examples 28 | 29 | These snippets cause lint errors. 30 | 31 | ```js 32 | let el =
; 33 | 34 | let el =
; 35 | 36 | let el =
; 37 | 38 | let el =
; 39 | 40 | let el =
; 41 | 42 | let el =
; 43 | 44 | let el = ( 45 |
}> 46 |
47 |
48 | ); 49 | 50 | let el =
; 51 | ``` 52 | 53 | ### Valid Examples 54 | 55 | These snippets don't cause lint errors. 56 | 57 | ```js 58 | let el =
; 59 | 60 | let el =
; 61 | 62 | let el =
; 63 | 64 | let el =
; 65 | 66 | let el =
; 67 | 68 | let el =
; 69 | 70 | let el =
} />; 71 | 72 | let el = ( 73 |
74 |
75 |
76 | ); 77 | ``` 78 | 79 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/docs/jsx-no-script-url.md: -------------------------------------------------------------------------------- 1 | 2 | # solid/jsx-no-script-url 3 | Disallow javascript: URLs. 4 | This rule is **an error** by default. 5 | 6 | [View source](../src/rules/jsx-no-script-url.ts) · [View tests](../test/rules/jsx-no-script-url.test.ts) 7 | 8 | 9 | See [this issue](https://github.com/solidjs-community/eslint-plugin-solid/issues/24) for rationale. 10 | 11 | 12 | 13 | 14 | 15 | 16 | ## Tests 17 | 18 | ### Invalid Examples 19 | 20 | These snippets cause lint errors. 21 | 22 | ```js 23 | let el = ; 24 | 25 | let el = ; 26 | 27 | let el = ; 28 | 29 | const link = "javascript:alert('hacked!')"; 30 | let el = ; 31 | 32 | const link = "\tj\na\tv\na\ts\nc\tr\ni\tpt:alert('hacked!')"; 33 | let el = ; 34 | 35 | const link = "javascrip" + "t:alert('hacked!')"; 36 | let el = ; 37 | ``` 38 | 39 | ### Valid Examples 40 | 41 | These snippets don't cause lint errors. 42 | 43 | ```js 44 | let el = ; 45 | 46 | let el = ; 47 | 48 | let el = ; 49 | 50 | const link = "https://example.com"; 51 | let el = ; 52 | ``` 53 | 54 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/docs/jsx-uses-vars.md: -------------------------------------------------------------------------------- 1 | 2 | # solid/jsx-uses-vars 3 | Prevent variables used in JSX from being marked as unused. 4 | This rule is **an error** by default. 5 | 6 | [View source](../src/rules/jsx-uses-vars.ts) · [View tests](../test/rules/jsx-uses-vars.test.ts) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/docs/no-array-handlers.md: -------------------------------------------------------------------------------- 1 | 2 | # solid/no-array-handlers 3 | Disallow usage of type-unsafe event handlers. 4 | This rule is **off** by default. 5 | 6 | [View source](../src/rules/no-array-handlers.ts) · [View tests](../test/rules/no-array-handlers.test.ts) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ## Tests 15 | 16 | ### Invalid Examples 17 | 18 | These snippets cause lint errors. 19 | 20 | ```js 21 | let el =
38 | ); 39 | 40 | function Component() { 41 | const arr = [(n: number) => n * n, 2]; 42 | return
; 43 | } 44 | ``` 45 | 46 | ### Valid Examples 47 | 48 | These snippets don't cause lint errors. 49 | 50 | ```js 51 | let el =
`, 56 | errors: [{ messageId: "noArrayHandlers" }], 57 | }, 58 | { 59 | code: `function Component() { 60 | const arr = [(n: number) => n*n, 2]; 61 | return
; 62 | }`, 63 | errors: [{ messageId: "noArrayHandlers" }], 64 | [tsOnly]: true, 65 | }, 66 | ], 67 | }); 68 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/test/rules/no-innerhtml.test.ts: -------------------------------------------------------------------------------- 1 | import { AST_NODE_TYPES as T } from "@typescript-eslint/utils"; 2 | import { run } from "../ruleTester"; 3 | import rule from "../../src/rules/no-innerhtml"; 4 | 5 | export const cases = run("no-innerhtml", rule, { 6 | valid: [ 7 | `let el =
Hello world!
`, 8 | `let el = Hello world!`, 9 | `let el =
`, 10 | `let el =
Hello

" + "

world!

"} />`, 11 | `let el =
`, 12 | ], 13 | invalid: [ 14 | { 15 | code: `let el =
`, 16 | options: [{ allowStatic: false }], 17 | errors: [{ messageId: "dangerous" }], 18 | }, 19 | { 20 | code: `let el =
Hello

world!

"} />`, 21 | options: [{ allowStatic: false }], 22 | errors: [{ messageId: "dangerous" }], 23 | }, 24 | { 25 | code: `let el =
Hello

" + "

world!

"} />`, 26 | options: [{ allowStatic: false }], 27 | errors: [{ messageId: "dangerous" }], 28 | }, 29 | { 30 | code: `let el =
`, 31 | errors: [{ messageId: "dangerous" }], 32 | }, 33 | { 34 | code: `let el =
`, 35 | errors: [ 36 | { 37 | messageId: "notHtml", 38 | suggestions: [ 39 | { 40 | messageId: "useInnerText", 41 | output: `let el =
`, 42 | }, 43 | ], 44 | }, 45 | ], 46 | }, 47 | { 48 | code: ` 49 | let el = ( 50 |
51 |

Child element content

52 |
53 | ); 54 | `, 55 | errors: [{ messageId: "conflict", type: T.JSXElement }], 56 | }, 57 | { 58 | code: ` 59 | let el = ( 60 |
61 |

Child element content 1

62 |

Child element context 2

63 |
64 | ); 65 | `, 66 | errors: [{ messageId: "conflict", type: T.JSXElement }], 67 | }, 68 | { 69 | code: ` 70 | let el = ( 71 |
72 | {"Child text content"} 73 |
74 | ); 75 | `, 76 | errors: [{ messageId: "conflict", type: T.JSXElement }], 77 | }, 78 | { 79 | code: ` 80 | let el = ( 81 |
82 | {identifier} 83 |
84 | ); 85 | `, 86 | errors: [{ messageId: "conflict", type: T.JSXElement }], 87 | }, 88 | { 89 | code: `let el =
Hello

world!

" }} />`, 90 | errors: [{ messageId: "dangerouslySetInnerHTML" }], 91 | output: `let el =
Hello

world!

"} />`, 92 | }, 93 | { 94 | code: `let el =
`, 95 | errors: [{ messageId: "dangerouslySetInnerHTML" }], 96 | }, 97 | { 98 | code: `let el =
`, 99 | errors: [{ messageId: "dangerouslySetInnerHTML" }], 100 | }, 101 | ], 102 | }); 103 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/test/rules/no-proxy-apis.test.ts: -------------------------------------------------------------------------------- 1 | import { AST_NODE_TYPES as T } from "@typescript-eslint/utils"; 2 | import { run } from "../ruleTester"; 3 | import rule from "../../src/rules/no-proxy-apis"; 4 | 5 | export const cases = run("no-proxy-apis", rule, { 6 | valid: [ 7 | `let merged = mergeProps({}, props);`, 8 | `const obj = {}; let merged = mergeProps(obj, props);`, 9 | `let obj = {}; let merged = mergeProps(obj, props);`, 10 | `let merged = mergeProps({ get asdf() { signal() } }, props);`, 11 | `let el =
`, 12 | `let el =
`, 13 | `let obj = { Proxy: 1 }`, 14 | ], 15 | invalid: [ 16 | { 17 | code: `let proxy = new Proxy(asdf, {});`, 18 | errors: [{ messageId: "proxyLiteral" }], 19 | }, 20 | { 21 | code: `let proxy = Proxy.revocable(asdf, {});`, 22 | errors: [{ messageId: "proxyLiteral" }], 23 | }, 24 | { 25 | code: `import {} from 'solid-js/store';`, 26 | errors: [{ messageId: "noStore", type: T.ImportDeclaration }], 27 | }, 28 | { 29 | code: `let el =
`, 30 | errors: [{ messageId: "spreadCall" }], 31 | }, 32 | { 33 | code: `let el =
`, 34 | errors: [{ messageId: "spreadCall" }], 35 | }, 36 | { 37 | code: `let el =
`, 38 | errors: [{ messageId: "spreadMember" }], 39 | }, 40 | { 41 | code: `let el =
`, 42 | errors: [{ messageId: "spreadMember" }], 43 | }, 44 | { 45 | code: `let merged = mergeProps(maybeSignal)`, 46 | errors: [{ messageId: "mergeProps" }], 47 | }, 48 | { 49 | code: `let func = () => ({}); let merged = mergeProps(func, props)`, 50 | errors: [{ messageId: "mergeProps" }], 51 | }, 52 | ], 53 | }); 54 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/test/rules/no-react-deps.test.ts: -------------------------------------------------------------------------------- 1 | import { run } from "../ruleTester"; 2 | import rule from "../../src/rules/no-react-deps"; 3 | 4 | export const cases = run("no-react-deps", rule, { 5 | valid: [ 6 | `createEffect(() => { 7 | console.log(signal()); 8 | });`, 9 | `createEffect((prev) => { 10 | console.log(signal()); 11 | return prev + 1; 12 | }, 0);`, 13 | `createEffect((prev) => { 14 | console.log(signal()); 15 | return (prev || 0) + 1; 16 | });`, 17 | `createEffect((prev) => { 18 | console.log(signal()); 19 | return prev ? prev + 1 : 1; 20 | }, undefined);`, 21 | `const value = createMemo(() => computeExpensiveValue(a(), b()));`, 22 | `const sum = createMemo((prev) => input() + prev, 0);`, 23 | `const args = [() => { console.log(signal()); }, [signal()]]; 24 | createEffect(...args);`, 25 | ], 26 | invalid: [ 27 | { 28 | code: `createEffect(() => { 29 | console.log(signal()); 30 | }, [signal()]);`, 31 | errors: [{ messageId: "noUselessDep", data: { name: "createEffect" } }], 32 | output: `createEffect(() => { 33 | console.log(signal()); 34 | }, );`, 35 | }, 36 | { 37 | code: `createEffect(() => { 38 | console.log(signal()); 39 | }, [signal]);`, 40 | errors: [{ messageId: "noUselessDep", data: { name: "createEffect" } }], 41 | output: `createEffect(() => { 42 | console.log(signal()); 43 | }, );`, 44 | }, 45 | { 46 | code: `const deps = [signal]; 47 | createEffect(() => { 48 | console.log(signal()); 49 | }, deps);`, 50 | errors: [{ messageId: "noUselessDep", data: { name: "createEffect" } }], 51 | // no `output` 52 | }, 53 | { 54 | code: `const value = createMemo(() => computeExpensiveValue(a(), b()), [a(), b()]);`, 55 | errors: [{ messageId: "noUselessDep", data: { name: "createMemo" } }], 56 | output: `const value = createMemo(() => computeExpensiveValue(a(), b()), );`, 57 | }, 58 | { 59 | code: `const value = createMemo(() => computeExpensiveValue(a(), b()), [a, b]);`, 60 | errors: [{ messageId: "noUselessDep", data: { name: "createMemo" } }], 61 | output: `const value = createMemo(() => computeExpensiveValue(a(), b()), );`, 62 | }, 63 | { 64 | code: `const value = createMemo(() => computeExpensiveValue(a(), b()), [a, b()]);`, 65 | errors: [{ messageId: "noUselessDep", data: { name: "createMemo" } }], 66 | output: `const value = createMemo(() => computeExpensiveValue(a(), b()), );`, 67 | }, 68 | { 69 | code: `const deps = [a, b]; 70 | const value = createMemo(() => computeExpensiveValue(a(), b()), deps);`, 71 | errors: [{ messageId: "noUselessDep", data: { name: "createMemo" } }], 72 | // no `output` 73 | }, 74 | { 75 | code: `const deps = [a, b]; 76 | const memoFn = () => computeExpensiveValue(a(), b()); 77 | const value = createMemo(memoFn, deps);`, 78 | errors: [{ messageId: "noUselessDep", data: { name: "createMemo" } }], 79 | // no `output` 80 | }, 81 | ], 82 | }); 83 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/test/rules/no-react-specific-props.test.ts: -------------------------------------------------------------------------------- 1 | import { run } from "../ruleTester"; 2 | import rule from "../../src/rules/no-react-specific-props"; 3 | 4 | export const cases = run("no-react-specific-props", rule, { 5 | valid: [ 6 | `let el =
Hello world!
;`, 7 | `let el =
Hello world!
;`, 8 | `let el =
Hello world!
;`, 9 | `let el =
Hello world!
;`, 10 | `let el = ;`, 11 | `let el = `, 12 | `let el = `, 13 | `let el = `, 14 | `let el = `, 15 | `let el = `, 16 | ], 17 | invalid: [ 18 | { 19 | code: `let el =
Hello world!
`, 20 | errors: [{ messageId: "prefer", data: { from: "className", to: "class" } }], 21 | output: `let el =
Hello world!
`, 22 | }, 23 | { 24 | code: `let el =
Hello world!
`, 25 | errors: [{ messageId: "prefer", data: { from: "className", to: "class" } }], 26 | output: `let el =
Hello world!
`, 27 | }, 28 | { 29 | code: `let el =
`, 30 | errors: [{ messageId: "prefer", data: { from: "className", to: "class" } }], 31 | output: `let el =
`, 32 | }, 33 | { 34 | code: `let el =
Hello world!
`, 35 | errors: [{ messageId: "prefer", data: { from: "className", to: "class" } }], 36 | output: `let el =
Hello world!
`, 37 | }, 38 | { 39 | code: `let el = Hello world!`, 40 | errors: [{ messageId: "prefer", data: { from: "className", to: "class" } }], 41 | output: `let el = Hello world!`, 42 | }, 43 | { 44 | code: `let el = `, 45 | errors: [{ messageId: "prefer", data: { from: "htmlFor", to: "for" } }], 46 | output: `let el = `, 47 | }, 48 | { 49 | code: `let el = `, 50 | errors: [{ messageId: "prefer", data: { from: "htmlFor", to: "for" } }], 51 | output: `let el = `, 52 | }, 53 | { 54 | code: `let el = `, 55 | errors: [{ messageId: "prefer", data: { from: "htmlFor", to: "for" } }], 56 | output: `let el = `, 57 | }, 58 | { 59 | code: `let el = Hello world!`, 60 | errors: [{ messageId: "prefer", data: { from: "htmlFor", to: "for" } }], 61 | output: `let el = Hello world!`, 62 | }, 63 | { 64 | code: `let el =
`, 65 | errors: [{ messageId: "noUselessKey" }], 66 | output: `let el =
`, 67 | }, 68 | ], 69 | }); 70 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/test/rules/no-unknown-namespaces.test.ts: -------------------------------------------------------------------------------- 1 | import { run } from "../ruleTester"; 2 | import rule from "../../src/rules/no-unknown-namespaces"; 3 | 4 | export const cases = run("no-unknown-namespaces", rule, { 5 | valid: [ 6 | `let el =
;`, 7 | `let el =
;`, 8 | `let el =
;`, 9 | `let el =
;`, 10 | `let el =
;`, 11 | `let el =
;`, 12 | `let el =
;`, 13 | `let el =
;`, 14 | `let el =
;`, 15 | `let el = `, 16 | { 17 | options: [{ allowedNamespaces: ["foo"] }], 18 | code: `let el = `, 19 | }, 20 | ], 21 | invalid: [ 22 | { 23 | code: `let el =
`, 24 | errors: [{ messageId: "unknown", data: { namespace: "foo" } }], 25 | }, 26 | { 27 | code: `let el =
`, 28 | errors: [{ messageId: "unknown", data: { namespace: "bar" } }], 29 | }, 30 | { 31 | code: `let el =
`, 32 | errors: [{ messageId: "style", data: { namespace: "style" } }], 33 | }, 34 | { 35 | code: `let el =
`, 36 | errors: [{ messageId: "style", data: { namespace: "style" } }], 37 | }, 38 | { 39 | code: `let el =
`, 40 | errors: [{ messageId: "style", data: { namespace: "class" } }], 41 | }, 42 | { 43 | code: `let el =
`, 44 | errors: [{ messageId: "style", data: { namespace: "class" } }], 45 | }, 46 | { 47 | code: `let el = `, 48 | errors: [ 49 | { 50 | messageId: "component", 51 | suggestions: [ 52 | { 53 | messageId: "component-suggest", 54 | data: { namespace: "attr", name: "foo" }, 55 | output: `let el = `, 56 | }, 57 | ], 58 | }, 59 | ], 60 | }, 61 | { 62 | code: `let el = `, 63 | errors: [ 64 | { 65 | messageId: "component", 66 | suggestions: [ 67 | { 68 | messageId: "component-suggest", 69 | data: { namespace: "foo", name: "boo" }, 70 | output: `let el = `, 71 | }, 72 | ], 73 | }, 74 | ], 75 | }, 76 | ], 77 | }); 78 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/test/rules/prefer-classlist.test.ts: -------------------------------------------------------------------------------- 1 | import { run } from "../ruleTester"; 2 | import rule from "../../src/rules/prefer-classlist"; 3 | 4 | export const cases = run("prefer-classlist", rule, { 5 | valid: [ 6 | `let el =
Hello, world!
`, 7 | `let el =
Hello, world!
`, 8 | `let el =
Hello, world!
`, 9 | `let el =
Hello, world!
`, 10 | `let el =
Hello, world!
`, 11 | `let el =
Hello, world!
`, 12 | `let el =
Hello, world!
`, 13 | `let el =
Hello, world!
`, 14 | `let el =
Hello, world!
`, 15 | `let el =
Hello, world!
`, 16 | { 17 | code: `let el =
Hello, world!
`, 18 | options: [{ classnames: ["x", "y", "z"] }], 19 | }, 20 | ], 21 | invalid: [ 22 | { 23 | code: `let el =
Hello, world!
`, 24 | errors: [{ messageId: "preferClasslist", data: { classnames: "cn" } }], 25 | output: `let el =
Hello, world!
`, 26 | }, 27 | { 28 | code: `let el =
Hello, world!
`, 29 | errors: [{ messageId: "preferClasslist", data: { classnames: "clsx" } }], 30 | output: `let el =
Hello, world!
`, 31 | }, 32 | { 33 | code: `let el =
Hello, world!
`, 34 | errors: [{ messageId: "preferClasslist", data: { classnames: "classnames" } }], 35 | output: `let el =
Hello, world!
`, 36 | }, 37 | { 38 | code: `let el =
Hello, world!
`, 39 | options: [{ classnames: ["x", "y", "z"] }], 40 | errors: [{ messageId: "preferClasslist", data: { classnames: "x" } }], 41 | output: `let el =
Hello, world!
`, 42 | }, 43 | { 44 | code: `let el =
Hello, world!
`, 45 | errors: [{ messageId: "preferClasslist", data: { classnames: "cn" } }], 46 | output: `let el =
Hello, world!
`, 47 | }, 48 | { 49 | code: `let el =
2 })}>Hello, world!
`, 50 | errors: [{ messageId: "preferClasslist", data: { classnames: "cn" } }], 51 | output: `let el =
2 }}>Hello, world!
`, 52 | }, 53 | ], 54 | }); 55 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/test/rules/prefer-for.test.ts: -------------------------------------------------------------------------------- 1 | import { run, tsOnly } from "../ruleTester"; 2 | import rule from "../../src/rules/prefer-for"; 3 | 4 | export const cases = run("prefer-for", rule, { 5 | valid: [ 6 | `let Component = (props) =>
    {d =>
  1. {d.text}
  2. }
;`, 7 | `let abc = x.map(y => y + z);`, 8 | `let Component = (props) => { 9 | let abc = x.map(y => y + z); 10 | return
Hello, world!
; 11 | }`, 12 | ], 13 | invalid: [ 14 | // fixes to add , which can be auto-imported in jsx-no-undef 15 | { 16 | code: `let Component = (props) =>
    {props.data.map(d =>
  1. {d.text}
  2. )}
;`, 17 | errors: [{ messageId: "preferFor" }], 18 | output: `let Component = (props) =>
    {d =>
  1. {d.text}
  2. }
;`, 19 | }, 20 | { 21 | code: `let Component = (props) => <>{props.data.map(d =>
  • {d.text}
  • )};`, 22 | errors: [{ messageId: "preferFor" }], 23 | output: `let Component = (props) => <>{d =>
  • {d.text}
  • }
    ;`, 24 | }, 25 | { 26 | code: `let Component = (props) =>
      {props.data.map(d =>
    1. {d.text}
    2. )}
    ;`, 27 | errors: [{ messageId: "preferFor" }], 28 | output: `let Component = (props) =>
      {d =>
    1. {d.text}
    2. }
    ;`, 29 | }, 30 | { 31 | code: ` 32 | function Component(props) { 33 | return
      {props.data.map(d =>
    1. {d.text}
    2. )}
    ; 34 | }`, 35 | errors: [{ messageId: "preferFor" }], 36 | output: ` 37 | function Component(props) { 38 | return
      {d =>
    1. {d.text}
    2. }
    ; 39 | }`, 40 | }, 41 | { 42 | code: ` 43 | function Component(props) { 44 | return
      {props.data?.map(d =>
    1. {d.text}
    2. )}
    ; 45 | }`, 46 | errors: [{ messageId: "preferFor" }], 47 | output: ` 48 | function Component(props) { 49 | return
      {{d =>
    1. {d.text}
    2. }
      }
    ; 50 | }`, 51 | [tsOnly]: true, 52 | }, 53 | // deopts 54 | { 55 | code: `let Component = (props) =>
      {props.data.map(() =>
    1. )}
    ;`, 56 | errors: [{ messageId: "preferForOrIndex" }], 57 | }, 58 | { 59 | code: `let Component = (props) =>
      {props.data.map((...args) =>
    1. {args[0].text}
    2. )}
    ;`, 60 | errors: [{ messageId: "preferForOrIndex" }], 61 | }, 62 | ], 63 | }); 64 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/test/rules/prefer-show.test.ts: -------------------------------------------------------------------------------- 1 | import { run } from "../ruleTester"; 2 | import rule from "../../src/rules/prefer-show"; 3 | 4 | export const cases = run("prefer-show", rule, { 5 | valid: [ 6 | `function Component(props) { 7 | return Content; 8 | }`, 9 | `function Component(props) { 10 | return Content; 11 | }`, 12 | ], 13 | invalid: [ 14 | { 15 | code: ` 16 | function Component(props) { 17 | return
    {props.cond && Content}
    ; 18 | }`, 19 | errors: [{ messageId: "preferShowAnd" }], 20 | output: ` 21 | function Component(props) { 22 | return
    Content
    ; 23 | }`, 24 | }, 25 | { 26 | code: ` 27 | function Component(props) { 28 | return <>{props.cond && Content}; 29 | }`, 30 | errors: [{ messageId: "preferShowAnd" }], 31 | output: ` 32 | function Component(props) { 33 | return <>Content; 34 | }`, 35 | }, 36 | { 37 | code: ` 38 | function Component(props) { 39 | return ( 40 |
    41 | {props.cond ? ( 42 | Content 43 | ) : ( 44 | Fallback 45 | )} 46 |
    47 | ); 48 | }`, 49 | errors: [{ messageId: "preferShowTernary" }], 50 | output: ` 51 | function Component(props) { 52 | return ( 53 |
    54 | Fallback}>Content 55 |
    56 | ); 57 | }`, 58 | }, 59 | // Check that it also works with control flow function children 60 | { 61 | code: ` 62 | function Component(props) { 63 | return ( 64 | 65 | {(listItem) => listItem.cond && Content} 66 | 67 | ); 68 | }`, 69 | errors: [{ messageId: "preferShowAnd" }], 70 | output: ` 71 | function Component(props) { 72 | return ( 73 | 74 | {(listItem) => Content} 75 | 76 | ); 77 | }`, 78 | }, 79 | { 80 | code: ` 81 | function Component(props) { 82 | return ( 83 | 84 | {(listItem) => (listItem.cond ? ( 85 | Content 86 | ) : ( 87 | Fallback 88 | ))} 89 | 90 | ); 91 | }`, 92 | errors: [{ messageId: "preferShowTernary" }], 93 | output: ` 94 | function Component(props) { 95 | return ( 96 | 97 | {(listItem) => (Fallback}>Content)} 98 | 99 | ); 100 | }`, 101 | }, 102 | ], 103 | }); 104 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/test/rules/self-closing-comp.test.ts: -------------------------------------------------------------------------------- 1 | import { run } from "../ruleTester"; 2 | import rule from "../../src/rules/self-closing-comp"; 3 | 4 | export const cases = run("self-closing-comp", rule, { 5 | valid: [ 6 | `let el = ;`, 7 | `let el = ;`, 8 | `let el = ;`, 9 | `let el = ;`, 10 | `let el = 11 | 12 | ;`, 13 | `let el = 14 | 15 | `, 16 | `let el = ;`, 17 | `let el = ;`, 18 | 19 | `let el = ;`, 20 | `let el =
     
    `, 21 | `let el =
    {' '}
    `, 22 | { 23 | code: `let el =
    ;`, 24 | options: [{ html: "none" }], 25 | }, 26 | { 27 | code: `let el = ;`, 28 | options: [{ html: "none" }], 29 | }, 30 | { 31 | code: `let el =
    ;`, 32 | options: [{ html: "void" }], 33 | }, 34 | { 35 | code: `let el = ( 36 |
    37 |
    38 | )`, 39 | options: [{ html: "none" }], 40 | }, 41 | { 42 | code: `let el = `, 43 | options: [{ component: "none" }], 44 | }, 45 | ], 46 | invalid: [ 47 | { 48 | code: `let el =
    ;`, 49 | errors: [{ messageId: "selfClose" }], 50 | output: `let el =
    ;`, 51 | }, 52 | { 53 | code: `let el = ;`, 54 | errors: [{ messageId: "selfClose" }], 55 | output: `let el = ;`, 56 | }, 57 | { 58 | code: `let el =
    ;`, 59 | options: [{ html: "void" }], 60 | errors: [{ messageId: "dontSelfClose" }], 61 | output: `let el =
    ;`, 62 | }, 63 | { 64 | code: `let el =
    ;`, 65 | options: [{ html: "void" }], 66 | errors: [{ messageId: "dontSelfClose" }], 67 | output: `let el =
    ;`, 68 | }, 69 | { 70 | code: `let el = ;`, 71 | options: [{ html: "none" }], 72 | errors: [{ messageId: "dontSelfClose" }], 73 | output: `let el = ;`, 74 | }, 75 | { 76 | code: `let el = ;`, 77 | options: [{ html: "none" }], 78 | errors: [{ messageId: "dontSelfClose" }], 79 | output: `let el = ;`, 80 | }, 81 | { 82 | code: `let el = ( 83 |
    84 |
    85 | );`, 86 | errors: [{ messageId: "selfClose" }], 87 | output: `let el = ( 88 |
    89 | );`, 90 | }, 91 | { 92 | code: `let el = ( 93 | 94 | 95 | );`, 96 | errors: [{ messageId: "selfClose" }], 97 | output: `let el = ( 98 | 99 | );`, 100 | }, 101 | { 102 | code: `let el = ;`, 103 | options: [{ component: "none" }], 104 | errors: [{ messageId: "dontSelfClose" }], 105 | output: `let el = ;`, 106 | }, 107 | ], 108 | }); 109 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig({ 4 | entry: ["src/index.ts", "src/configs/recommended.ts", "src/configs/typescript.ts"], 5 | format: ["cjs", "esm"], 6 | dts: true, 7 | // experimentalDts: true, 8 | sourcemap: true, 9 | clean: true, 10 | }); 11 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/vitest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | test: { 3 | globals: true, 4 | setupFiles: ["vitest.setup.js"], 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/eslint-plugin-solid/vitest.setup.js: -------------------------------------------------------------------------------- 1 | import { vi } from "vitest"; 2 | 3 | // Don't bother checking for imports for every test 4 | vi.mock("./src/utils", async (importOriginal) => { 5 | return { 6 | ...(await importOriginal()), 7 | trackImports: () => { 8 | const handleImportDeclaration = () => {}; 9 | const matchImport = (imports, str) => { 10 | const importArr = Array.isArray(imports) ? imports : [imports]; 11 | return importArr.find((i) => i === str); 12 | }; 13 | return { matchImport, handleImportDeclaration }; 14 | }, 15 | }; 16 | }); 17 | -------------------------------------------------------------------------------- /packages/eslint-solid-standalone/README.md: -------------------------------------------------------------------------------- 1 | # eslint-solid-standalone 2 | 3 | This package bundles ESLint, eslint-plugin-solid, and necessary tooling 4 | together into one package that can be used on the web or in a web worker. 5 | 6 | It mainly exists to power ESLint support in the Solid playground. 7 | 8 | Heavily inspired/lifted from [@typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/website-eslint)'s equivalent setup. 9 | 10 | ## API 11 | 12 | ```ts 13 | export function verify( 14 | code: string, 15 | ruleSeverityOverrides?: Record 16 | ): Linter.LintMessage[]; 17 | 18 | export function verifyAndFix( 19 | code: string, 20 | ruleSeverityOverrides?: Record 21 | ): Linter.FixReport; 22 | 23 | export { plugin }; // eslint-plugin-solid 24 | export { pluginVersion, eslintVersion }; 25 | ``` 26 | 27 | `code`: a string of source code, supports TS and JSX 28 | 29 | `ruleSeverityOverrides`: an optional record of rule id (i.e. "solid/reactivity") to severity (i.e. 0, 1, or 2) 30 | -------------------------------------------------------------------------------- /packages/eslint-solid-standalone/dist.d.ts: -------------------------------------------------------------------------------- 1 | import type { ESLint, Linter } from "eslint"; 2 | export type { ESLint, Linter }; 3 | 4 | export declare const plugin: ESLint.Plugin; 5 | export declare const pluginVersion: string; 6 | export declare const eslintVersion: string; 7 | 8 | export declare function verify( 9 | code: string, 10 | ruleSeverityOverrides?: Record 11 | ): ReturnType; 12 | export declare function verifyAndFix( 13 | code: string, 14 | ruleSeverityOverrides?: Record 15 | ): ReturnType; 16 | -------------------------------------------------------------------------------- /packages/eslint-solid-standalone/index.js: -------------------------------------------------------------------------------- 1 | import { Linter } from "eslint/linter"; 2 | import * as parser from "@typescript-eslint/parser"; 3 | import plugin from "eslint-plugin-solid"; 4 | import { version as pluginVersion } from "eslint-plugin-solid/package.json"; 5 | import memoizeOne from "memoize-one"; 6 | 7 | // Create linter instance 8 | const linter = new Linter({ configType: "flat" }); 9 | 10 | const getConfig = memoizeOne((ruleSeverityOverrides) => { 11 | const config = [ 12 | { 13 | languageOptions: { 14 | parser, 15 | parserOptions: { 16 | ecmaFeatures: { 17 | jsx: true, 18 | }, 19 | }, 20 | }, 21 | ...plugin.configs["flat/typescript"], 22 | }, 23 | ]; 24 | if (ruleSeverityOverrides) { 25 | // change severity levels of rules based on rules: Record arg 26 | Object.keys(ruleSeverityOverrides).forEach((key) => { 27 | if (Object.prototype.hasOwnProperty.call(config[0].rules, key)) { 28 | if (Array.isArray(config[0].rules[key])) { 29 | config[0].rules[key] = [ruleSeverityOverrides[key], ...config[0].rules[key].slice(1)]; 30 | } else { 31 | config[0].rules[key] = ruleSeverityOverrides[key]; 32 | } 33 | } 34 | }); 35 | } 36 | return config; 37 | }); 38 | 39 | linter.verify = memoizeOne(linter.verify); 40 | linter.verifyAndFix = memoizeOne(linter.verifyAndFix); 41 | 42 | export { plugin, pluginVersion }; 43 | export const eslintVersion = linter.version; 44 | 45 | export function verify(code, ruleSeverityOverrides) { 46 | const config = getConfig(ruleSeverityOverrides); 47 | return linter.verify(code, config); 48 | } 49 | 50 | export function verifyAndFix(code, ruleSeverityOverrides) { 51 | const config = getConfig(ruleSeverityOverrides); 52 | return linter.verifyAndFix(code, config); 53 | } 54 | -------------------------------------------------------------------------------- /packages/eslint-solid-standalone/mock/empty.js: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /packages/eslint-solid-standalone/mock/glob-parent.js: -------------------------------------------------------------------------------- 1 | module.exports = () => ""; 2 | -------------------------------------------------------------------------------- /packages/eslint-solid-standalone/mock/globby.js: -------------------------------------------------------------------------------- 1 | export function sync() { 2 | // the website config is static and doesn't use glob config 3 | return []; 4 | } 5 | -------------------------------------------------------------------------------- /packages/eslint-solid-standalone/mock/is-glob.js: -------------------------------------------------------------------------------- 1 | export default function isGlob() { 2 | // the website config is static and doesn't use glob config 3 | return false; 4 | } 5 | -------------------------------------------------------------------------------- /packages/eslint-solid-standalone/mock/semver.js: -------------------------------------------------------------------------------- 1 | import satisfies from "semver/functions/satisfies"; 2 | import major from "semver/functions/major"; 3 | 4 | // just in case someone adds a import * as semver usage 5 | export { satisfies, major }; 6 | 7 | export default { satisfies, major }; 8 | -------------------------------------------------------------------------------- /packages/eslint-solid-standalone/mock/util.js: -------------------------------------------------------------------------------- 1 | const util = {}; 2 | 3 | util.inspect = function (value) { 4 | return value; 5 | }; 6 | 7 | export default util; 8 | -------------------------------------------------------------------------------- /packages/eslint-solid-standalone/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-solid-standalone", 3 | "version": "0.14.5", 4 | "description": "A bundle with eslint and eslint-plugin-solid that can be used in the browser.", 5 | "repository": "https://github.com/solidjs-community/eslint-plugin-solid", 6 | "license": "MIT", 7 | "author": "Josh Wilson ", 8 | "type": "module", 9 | "main": "dist.js", 10 | "types": "dist.d.ts", 11 | "files": [ 12 | "dist.js", 13 | "dist.d.ts", 14 | "dist.js.map", 15 | "README.md" 16 | ], 17 | "scripts": { 18 | "build": "rollup --config=rollup.config.mjs", 19 | "test": "node --experimental-vm-modules ./test.mjs", 20 | "turbo:build": "rollup --config=rollup.config.mjs", 21 | "turbo:test": "node --experimental-vm-modules ./test.mjs" 22 | }, 23 | "dependencies": { 24 | "@types/eslint": "^8.56.7" 25 | }, 26 | "devDependencies": { 27 | "@rollup/plugin-commonjs": "^25.0.7", 28 | "@rollup/plugin-json": "^6.1.0", 29 | "@rollup/plugin-node-resolve": "^15.2.3", 30 | "@rollup/pluginutils": "^5.1.0", 31 | "@typescript-eslint/parser": "^6.21.0", 32 | "@typescript-eslint/utils": "^6.21.0", 33 | "eslint": "8.57.0", 34 | "eslint-plugin-solid": "workspace:^", 35 | "magic-string": "^0.30.8", 36 | "memoize-one": "^6.0.0", 37 | "rollup": "^3.29.5", 38 | "semver": "^7.6.0", 39 | "typescript": "~5.3.3" 40 | }, 41 | "peerDependencies": { 42 | "typescript": ">=4.0.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/eslint-solid-standalone/rollup-plugin-replace.mjs: -------------------------------------------------------------------------------- 1 | // lifted from @typescript-eslint/website-eslint/rollup-plugin/replace.js 2 | import path from "path"; 3 | import Module from "module"; 4 | import { createFilter } from "@rollup/pluginutils"; 5 | import MagicString from "magic-string"; 6 | import { createRequire } from "node:module"; 7 | 8 | const require = createRequire(import.meta.url); 9 | 10 | function toAbsolute(id) { 11 | return id.startsWith("./") ? path.resolve(id) : require.resolve(id); 12 | } 13 | 14 | function log(opts, message, type = "info") { 15 | if (opts.verbose) { 16 | console.log("rollup-plugin-replace > [" + type + "]", message); 17 | } 18 | } 19 | 20 | function createMatcher(it) { 21 | if (typeof it === "function") { 22 | return it; 23 | } else { 24 | return createFilter(it); 25 | } 26 | } 27 | 28 | export default (options = {}) => { 29 | const aliasesCache = new Map(); 30 | const aliases = (options.alias || []).map((item) => { 31 | return { 32 | match: item.match, 33 | matcher: createMatcher(item.match), 34 | target: item.target, 35 | absoluteTarget: toAbsolute(item.target), 36 | }; 37 | }); 38 | const replaces = (options.replace || []).map((item) => { 39 | return { 40 | match: item.match, 41 | test: item.test, 42 | replace: typeof item.replace === "string" ? () => item.replace : item.replace, 43 | 44 | matcher: createMatcher(item.match), 45 | }; 46 | }); 47 | 48 | return { 49 | name: "rollup-plugin-replace", 50 | resolveId(id, importerPath) { 51 | const importeePath = 52 | id.startsWith("./") || id.startsWith("../") 53 | ? Module.createRequire(importerPath).resolve(id) 54 | : id; 55 | 56 | let result = aliasesCache.get(importeePath); 57 | if (result) { 58 | return result; 59 | } 60 | 61 | result = aliases.find((item) => item.matcher(importeePath)); 62 | if (result) { 63 | aliasesCache.set(importeePath, result.absoluteTarget); 64 | log(options, `${importeePath} as ${result.target}`, "resolve"); 65 | return result.absoluteTarget; 66 | } 67 | 68 | return null; 69 | }, 70 | transform(code, id) { 71 | let hasReplacements = false; 72 | let magicString = new MagicString(code); 73 | 74 | replaces.forEach((item) => { 75 | if (item.matcher && !item.matcher(id)) { 76 | return; 77 | } 78 | 79 | let match = item.test.exec(code); 80 | let start, end; 81 | while (match) { 82 | hasReplacements = true; 83 | start = match.index; 84 | end = start + match[0].length; 85 | magicString.overwrite(start, end, item.replace(match)); 86 | match = item.test.global ? item.test.exec(code) : null; 87 | } 88 | }); 89 | 90 | if (!hasReplacements) { 91 | return; 92 | } 93 | log(options, id, "replace"); 94 | 95 | const map = magicString.generateMap(); 96 | return { code: magicString.toString(), map: map.toString() }; 97 | }, 98 | }; 99 | }; 100 | -------------------------------------------------------------------------------- /packages/eslint-solid-standalone/test.mjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | import path from "node:path"; 3 | import fs from "node:fs"; 4 | import assert from "node:assert"; 5 | import vm from "node:vm"; 6 | import typescript from "typescript"; 7 | 8 | /** 9 | * Test that dist.js can be run in a clean environment without Node or browser APIs, that it won't 10 | * crash, and that it will produce expected results. Code in, lints/fixes out is all it needs to do. 11 | */ 12 | 13 | // inject assert and a hidden _TYPESCRIPT_GLOBAL into global scope 14 | const context = vm.createContext({ 15 | assert, 16 | structuredClone, 17 | _TYPESCRIPT_GLOBAL: typescript, 18 | }); 19 | 20 | // create a module with the standalone build 21 | const code = fs.readFileSync(path.resolve("dist.js"), "utf-8"); 22 | const dist = new vm.SourceTextModule(code, { identifier: "dist.js", context }); 23 | 24 | // create a module reexporting typescript, a peer dependency of the standalone build 25 | const ts = new vm.SourceTextModule("export default _TYPESCRIPT_GLOBAL", { 26 | identifier: "typescript", 27 | context, 28 | }); 29 | 30 | // create a module that tests the build with `assert` 31 | const test = new vm.SourceTextModule( 32 | ` 33 | import { plugin, pluginVersion, eslintVersion, verify, verifyAndFix } from "dist.js"; 34 | 35 | // check no Node APIs are present, except injected 'assert' and '_TYPESCRIPT_GLOBAL' 36 | assert.equal(Object.keys(globalThis).length, 3); 37 | assert.equal(typeof assert, 'function'); 38 | assert.equal(typeof process, 'undefined'); 39 | assert.equal(typeof __dirname, 'undefined'); 40 | 41 | // check for presence of exported variables 42 | assert.equal(typeof plugin, "object"); 43 | assert.equal(typeof pluginVersion, "string"); 44 | assert.equal(typeof eslintVersion, "string"); 45 | assert.equal(eslintVersion[0], '8') 46 | assert.equal(typeof verify, "function"); 47 | assert.equal(typeof verifyAndFix, "function"); 48 | 49 | // ensure that the standalone runs without crashing and returns results 50 | assert.deepStrictEqual( 51 | verify('let el =
    ', { 'solid/no-react-specific-props': 2 }), 52 | [{ 53 | ruleId: "solid/no-react-specific-props", 54 | severity: 2, 55 | message: "Prefer the \`class\` prop over the deprecated \`className\` prop.", 56 | line: 1, 57 | column: 15, 58 | nodeType: "JSXAttribute", 59 | messageId: "prefer", 60 | endLine: 1, 61 | endColumn: 30, 62 | fix: { range: [14, 23], text: "class" }, 63 | }], 64 | ); 65 | assert.deepStrictEqual(verifyAndFix('let el =
    '), { 66 | fixed: true, 67 | messages: [], 68 | output: 'let el =
    ', 69 | }); 70 | `, 71 | { identifier: "test.mjs", context } 72 | ); 73 | 74 | // resolve imports to created modules, disallow any other attempts to import 75 | const linker = (specifier) => { 76 | const mod = { 77 | typescript: ts, 78 | "dist.js": dist, 79 | }[specifier]; 80 | if (!mod) { 81 | throw new Error(`can't import other modules: ${specifier}`); 82 | } 83 | return mod; 84 | }; 85 | await Promise.all([dist.link(linker), test.link(linker), ts.link(linker)]); 86 | 87 | // run the test module 88 | await test.evaluate({ timeout: 10 * 1000 }); 89 | assert.equal(test.status, "evaluated"); 90 | -------------------------------------------------------------------------------- /patches/eslint@8.57.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/package.json b/package.json 2 | index a51b58b2444f41ead8b445f27003245b451f5728..48820932d33a3671c07e0f909017a2894d3ace06 100644 3 | --- a/package.json 4 | +++ b/package.json 5 | @@ -10,7 +10,8 @@ 6 | "exports": { 7 | "./package.json": "./package.json", 8 | ".": "./lib/api.js", 9 | - "./use-at-your-own-risk": "./lib/unsupported-api.js" 10 | + "./use-at-your-own-risk": "./lib/unsupported-api.js", 11 | + "./linter": "./lib/linter/index.js" 12 | }, 13 | "scripts": { 14 | "build:docs:update-links": "node tools/fetch-docs-links.js", 15 | @@ -175,5 +176,6 @@ 16 | "license": "MIT", 17 | "engines": { 18 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 19 | - } 20 | + }, 21 | + "sideEffects": false 22 | } 23 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/*" 3 | - "test" 4 | -------------------------------------------------------------------------------- /scripts/version.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs/promises"; 2 | import path from "node:path"; 3 | import inc from "semver/functions/inc.js"; 4 | import { exec } from "node:child_process"; 5 | 6 | const pluginPackageJsonPath = path.resolve("packages", "eslint-plugin-solid", "package.json"); 7 | const standalonePackageJsonPath = path.resolve( 8 | "packages", 9 | "eslint-solid-standalone", 10 | "package.json" 11 | ); 12 | 13 | const pluginPackageJson = JSON.parse(await fs.readFile(pluginPackageJsonPath, "utf-8")); 14 | const standalonePackageJson = JSON.parse(await fs.readFile(standalonePackageJsonPath, "utf-8")); 15 | 16 | const version = pluginPackageJson.version; 17 | const increment = process.argv[2]; 18 | const newVersion = inc(version, increment); 19 | 20 | if (newVersion == null || !/^\d+\.\d+\.\d+$/.test(newVersion)) { 21 | console.error("Usage: node scripts/version.js [increment]"); 22 | process.exit(1); 23 | } 24 | 25 | pluginPackageJson.version = newVersion; 26 | standalonePackageJson.version = newVersion; 27 | 28 | await Promise.all([ 29 | fs.writeFile(pluginPackageJsonPath, JSON.stringify(pluginPackageJson, null, 2) + "\n", "utf-8"), 30 | fs.writeFile( 31 | standalonePackageJsonPath, 32 | JSON.stringify(standalonePackageJson, null, 2) + "\n", 33 | "utf-8" 34 | ), 35 | ]); 36 | await new Promise((resolve, reject) => { 37 | // Don't create a tag. It's better to wait until this PR is merged, and a tag can be created from 38 | // the GitHub UI (the whole point of versioning + publishing from GitHub). 39 | exec(`git commit --all --message="v${newVersion}"`, (error, stdout, stderr) => { 40 | if (error) { 41 | reject(error); 42 | } else { 43 | console.log(stdout); 44 | console.log(stderr); 45 | resolve(stdout); 46 | } 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | Not a real package—just here to test `eslint-plugin-solid` as a built package, i.e. types, export 2 | conditions, etc. 3 | -------------------------------------------------------------------------------- /test/eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import tseslint from "typescript-eslint"; 4 | import globals from "globals"; 5 | import recommendedConfig from "eslint-plugin-solid/configs/recommended"; 6 | 7 | export default tseslint.config({ 8 | files: ["{valid,invalid}/**/*.{js,jsx,ts,tsx}"], 9 | ...recommendedConfig, 10 | languageOptions: { 11 | globals: globals.browser, 12 | parser: tseslint.parser, 13 | parserOptions: { 14 | project: null, 15 | }, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /test/eslint.config.prefixed.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import tseslint from "typescript-eslint"; 4 | import globals from "globals"; 5 | import solid from "eslint-plugin-solid"; 6 | 7 | export default tseslint.config({ 8 | files: ["{valid,invalid}/**/*.{js,jsx,ts,tsx}"], 9 | ...solid.configs["flat/recommended"], 10 | languageOptions: { 11 | globals: globals.browser, 12 | parser: tseslint.parser, 13 | parserOptions: { 14 | project: null, 15 | }, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /test/fixture.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | 3 | import path from "path"; 4 | import { ESLint as FlatESLint } from "eslint"; 5 | import { ESLint as LegacyESLint } from "eslint-v8"; 6 | 7 | const cwd = __dirname; 8 | const validDir = path.join(cwd, "valid"); 9 | const jsxUndefPath = path.join(cwd, "invalid", "jsx-undef.jsx"); 10 | 11 | const checkResult = (result: LegacyESLint.LintResult | FlatESLint.LintResult) => { 12 | if (result.filePath.startsWith(validDir)) { 13 | expect(result.messages).toEqual([]); 14 | expect(result.errorCount).toBe(0); 15 | expect(result.warningCount).toBe(0); 16 | expect(result.usedDeprecatedRules).toEqual([]); 17 | } else { 18 | expect(result.messages).not.toEqual([]); 19 | expect(result.warningCount + result.errorCount).toBeGreaterThan(0); 20 | expect(result.usedDeprecatedRules).toEqual([]); 21 | 22 | if (result.filePath === jsxUndefPath) { 23 | // test for one specific error message 24 | expect(result.messages.some((message) => /'Component' is not defined/.test(message.message))); 25 | } 26 | } 27 | }; 28 | 29 | test.concurrent("fixture (legacy)", async () => { 30 | const eslint = new LegacyESLint({ 31 | cwd, 32 | baseConfig: { 33 | root: true, 34 | parser: "@typescript-eslint/parser", 35 | env: { browser: true }, 36 | plugins: ["solid"], 37 | extends: "plugin:solid/recommended", 38 | }, 39 | useEslintrc: false, 40 | }); 41 | const results = await eslint.lintFiles("{valid,invalid}/**/*.{js,jsx,ts,tsx}"); 42 | 43 | results.forEach(checkResult); 44 | 45 | expect(results.filter((result) => result.filePath === jsxUndefPath).length).toBe(1); 46 | }); 47 | 48 | test.concurrent('fixture (.configs["flat/recommended"])', async () => { 49 | const eslint = new FlatESLint({ 50 | cwd, 51 | overrideConfigFile: "./eslint.config.prefixed.js", 52 | } as any); 53 | const results = await eslint.lintFiles("{valid,invalid}/**/*.{js,jsx,ts,tsx}"); 54 | 55 | results.forEach(checkResult); 56 | 57 | expect(results.filter((result) => result.filePath === jsxUndefPath).length).toBe(1); 58 | }); 59 | 60 | test.concurrent("fixture (/configs/recommended)", async () => { 61 | const eslint = new FlatESLint({ 62 | cwd, 63 | overrideConfigFile: "./eslint.config.js", 64 | // ignorePatterns: ["eslint.*"], 65 | } as any); 66 | 67 | const results = await eslint.lintFiles("{valid,invalid}/**/*.{js,jsx,ts,tsx}"); 68 | 69 | results.forEach(checkResult); 70 | 71 | expect(results.filter((result) => result.filePath === jsxUndefPath).length).toBe(1); 72 | }); 73 | -------------------------------------------------------------------------------- /test/format.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, expectTypeOf } from "vitest"; 2 | 3 | import recommendedConfig from "eslint-plugin-solid/configs/recommended"; 4 | import typescriptConfig from "eslint-plugin-solid/configs/typescript"; 5 | import plugin from "eslint-plugin-solid"; 6 | import type * as standalone from "eslint-solid-standalone"; 7 | 8 | test("flat config has meta", () => { 9 | expect(recommendedConfig.plugins.solid.meta.name).toBe("eslint-plugin-solid"); 10 | expect(recommendedConfig.plugins.solid.meta.version).toEqual(expect.any(String)); 11 | expect(typescriptConfig.plugins.solid.meta.name).toBe("eslint-plugin-solid"); 12 | expect(typescriptConfig.plugins.solid.meta.version).toEqual(expect.any(String)); 13 | }); 14 | 15 | test('flat configs are also exposed on plugin.configs["flat/*"]', () => { 16 | // include flat configs on legacy config object with `flat/` prefix. 17 | expect(plugin.configs["flat/recommended"]).toBe(recommendedConfig); 18 | expect(plugin.configs["flat/typescript"]).toBe(typescriptConfig); 19 | }); 20 | 21 | test("legacy configs use strings, not modules", () => { 22 | expect(plugin.configs.recommended.plugins).toStrictEqual(["solid"]); 23 | expect(plugin.configs.typescript.plugins).toStrictEqual(["solid"]); 24 | }); 25 | 26 | test("plugin exposes sane export types", () => { 27 | expectTypeOf().toBeObject(); 28 | expectTypeOf().toBeObject(); 29 | expectTypeOf().toBeObject(); 30 | }); 31 | 32 | test("standalone exposes sane export types", () => { 33 | expectTypeOf().toBeFunction(); 34 | expectTypeOf().toBeString(); 35 | }); 36 | -------------------------------------------------------------------------------- /test/invalid/jsx-undef.jsx: -------------------------------------------------------------------------------- 1 | let el = ; 2 | -------------------------------------------------------------------------------- /test/invalid/reactivity-renaming.jsx: -------------------------------------------------------------------------------- 1 | import { createSignal as fooBar } from "solid-js"; 2 | 3 | const [signal] = fooBar(5); 4 | console.log(signal()); 5 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "test": "vitest --run", 7 | "turbo:test": "vitest --run" 8 | }, 9 | "devDependencies": { 10 | "@types/eslint-v8": "npm:@types/eslint@8", 11 | "eslint": "^9.5.0", 12 | "eslint-plugin-solid": "workspace:^", 13 | "eslint-solid-standalone": "workspace:^", 14 | "eslint-v8": "npm:eslint@8", 15 | "vitest": "^1.5.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/valid/async/lazy-components/greeting.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | export default function Greeting(props) { 3 | return

    Hi, {props.name}

    ; 4 | } 5 | -------------------------------------------------------------------------------- /test/valid/async/lazy-components/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { lazy } from "solid-js"; 4 | 5 | const Greeting = lazy(() => import("./greeting")); 6 | 7 | function App() { 8 | return ( 9 | <> 10 |

    Welcome

    11 | 12 | 13 | ); 14 | } 15 | 16 | render(() => , document.getElementById("app")); 17 | -------------------------------------------------------------------------------- /test/valid/async/resources/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { createSignal, createResource } from "solid-js"; 3 | import { render } from "solid-js/web"; 4 | 5 | const fetchUser = async (id) => (await fetch(`https://swapi.dev/api/people/${id}/`)).json(); 6 | 7 | const App = () => { 8 | const [userId, setUserId] = createSignal(); 9 | const [user] = createResource(userId, fetchUser); 10 | 11 | return ( 12 | <> 13 | setUserId(e.currentTarget.value)} 18 | /> 19 | {user.loading && "Loading..."} 20 |
    21 |
    {JSON.stringify(user(), null, 2)}
    22 |
    23 | 24 | ); 25 | }; 26 | 27 | render(App, document.getElementById("app")); 28 | -------------------------------------------------------------------------------- /test/valid/async/suspense-list/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { Suspense } from "solid-js"; 4 | 5 | import fetchProfileData from "./mock-api"; 6 | import ProfilePage from "./profile"; 7 | 8 | const App = () => { 9 | const { user, posts, trivia } = fetchProfileData(); 10 | return ( 11 | Loading...}> 12 | 13 | 14 | ); 15 | }; 16 | 17 | render(App, document.getElementById("app")); 18 | -------------------------------------------------------------------------------- /test/valid/async/suspense-list/mock-api.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { createResource } from "solid-js"; 3 | 4 | export default function fetchProfileData() { 5 | const [user] = createResource(fetchUser); 6 | const [posts] = createResource(fetchPosts); 7 | const [trivia] = createResource(fetchTrivia); 8 | return { user, posts, trivia }; 9 | } 10 | 11 | function fetchUser() { 12 | console.log("fetch user..."); 13 | return new Promise((resolve) => { 14 | setTimeout(() => { 15 | console.log("fetched user"); 16 | resolve({ 17 | name: "Ringo Starr", 18 | }); 19 | }, 500); 20 | }); 21 | } 22 | 23 | const ringoPosts = [ 24 | { 25 | id: 0, 26 | text: "I get by with a little help from my friends", 27 | }, 28 | { 29 | id: 1, 30 | text: "I'd like to be under the sea in an octupus's garden", 31 | }, 32 | { 33 | id: 2, 34 | text: "You got that sand all over your feet", 35 | }, 36 | ]; 37 | 38 | function fetchPosts() { 39 | const ringoPostsAtTheTime = ringoPosts; 40 | console.log("fetch posts..."); 41 | return new Promise((resolve) => { 42 | setTimeout(() => { 43 | console.log("fetched posts"); 44 | resolve(ringoPostsAtTheTime); 45 | }, 3000 * Math.random()); 46 | }); 47 | } 48 | 49 | function fetchTrivia() { 50 | console.log("fetch trivia..."); 51 | return new Promise((resolve) => { 52 | setTimeout(() => { 53 | console.log("fetched trivia"); 54 | resolve([ 55 | { 56 | id: 1, 57 | text: 'The nickname "Ringo" came from his habit of wearing numerous rings.', 58 | }, 59 | { 60 | id: 2, 61 | text: "Plays the drums left-handed with a right-handed drum set.", 62 | }, 63 | { 64 | id: 3, 65 | text: "Nominated for one Daytime Emmy Award, but did not win", 66 | }, 67 | ]); 68 | }, 3000 * Math.random()); 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /test/valid/async/suspense-list/profile.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { For, Suspense, SuspenseList } from "solid-js"; 3 | 4 | const ProfileDetails = (props) =>

    {props.user?.name}

    ; 5 | 6 | const ProfileTimeline = (props) => ( 7 |
      8 | {(post) =>
    • {post.text}
    • }
      9 |
    10 | ); 11 | 12 | const ProfileTrivia = (props) => ( 13 | <> 14 |

    Fun Facts

    15 |
      16 | {(fact) =>
    • {fact.text}
    • }
      17 |
    18 | 19 | ); 20 | 21 | const ProfilePage = (props) => ( 22 | 23 | 24 | Loading posts...}> 25 | 26 | 27 | Loading fun facts...}> 28 | 29 | 30 | 31 | ); 32 | 33 | export default ProfilePage; 34 | -------------------------------------------------------------------------------- /test/valid/async/suspense/greeting.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/eslint-plugin-solid/8a17adecb2451bb1cacf8add07d68f2a49bb3614/test/valid/async/suspense/greeting.tsx -------------------------------------------------------------------------------- /test/valid/async/suspense/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { lazy, Suspense } from "solid-js"; 4 | 5 | const Greeting = lazy(async () => { 6 | // simulate delay 7 | await new Promise((r) => setTimeout(r, 1000)); 8 | return import("./greeting"); 9 | }); 10 | 11 | function App() { 12 | return ( 13 | <> 14 |

    Welcome

    15 | Loading...

    }> 16 | 17 |
    18 | 19 | ); 20 | } 21 | 22 | render(() => , document.getElementById("app")); 23 | -------------------------------------------------------------------------------- /test/valid/async/transitions/child.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { createResource } from "solid-js"; 3 | 4 | const CONTENT = { 5 | Uno: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`, 6 | Dos: `Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?`, 7 | Tres: `On the other hand, we denounce with righteous indignation and dislike men who are so beguiled and demoralized by the charms of pleasure of the moment, so blinded by desire, that they cannot foresee the pain and trouble that are bound to ensue; and equal blame belongs to those who fail in their duty through weakness of will, which is the same as saying through shrinking from toil and pain. These cases are perfectly simple and easy to distinguish. In a free hour, when our power of choice is untrammelled and when nothing prevents our being able to do what we like best, every pleasure is to be welcomed and every pain avoided. But in certain circumstances and owing to the claims of duty or the obligations of business it will frequently occur that pleasures have to be repudiated and annoyances accepted. The wise man therefore always holds in these matters to this principle of selection: he rejects pleasures to secure other greater pleasures, or else he endures pains to avoid worse pains.`, 8 | }; 9 | 10 | function createDelay() { 11 | return new Promise((resolve) => { 12 | const delay = Math.random() * 420 + 160; 13 | setTimeout(() => resolve(delay), delay); 14 | }); 15 | } 16 | 17 | const Child = (props) => { 18 | const [time] = createResource(createDelay); 19 | 20 | return ( 21 |
    22 | This content is for page "{props.page}" after {time()?.toFixed()}ms. 23 |

    {CONTENT[props.page]}

    24 |
    25 | ); 26 | }; 27 | 28 | export default Child; 29 | -------------------------------------------------------------------------------- /test/valid/async/transitions/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { createSignal, Suspense, Switch, Match, useTransition } from "solid-js"; 3 | import { render } from "solid-js/web"; 4 | import Child from "./child"; 5 | 6 | const App = () => { 7 | const [tab, setTab] = createSignal(0); 8 | const [pending, start] = useTransition(); 9 | const updateTab = (index) => () => start(() => setTab(index)); 10 | 11 | return ( 12 | <> 13 |
      14 |
    • 15 | Uno 16 |
    • 17 |
    • 18 | Dos 19 |
    • 20 |
    • 21 | Tres 22 |
    • 23 |
    24 |
    25 | Loading...
    }> 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
    39 | 40 | ); 41 | }; 42 | 43 | render(App, document.getElementById("app")); 44 | -------------------------------------------------------------------------------- /test/valid/bindings/classlist/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal } from "solid-js"; 4 | 5 | import "./style.css"; 6 | 7 | function App() { 8 | const [current, setCurrent] = createSignal("foo"); 9 | 10 | return ( 11 | <> 12 | 15 | 18 | 21 | 22 | ); 23 | } 24 | 25 | render(() => , document.getElementById("app")); 26 | -------------------------------------------------------------------------------- /test/valid/bindings/directives/click-outside.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { onCleanup } from "solid-js"; 3 | 4 | export default function clickOutside(el, accessor) { 5 | const onClick = (e) => !el.contains(e.target) && accessor()?.(); 6 | document.body.addEventListener("click", onClick); 7 | 8 | onCleanup(() => document.body.removeEventListener("click", onClick)); 9 | } 10 | -------------------------------------------------------------------------------- /test/valid/bindings/directives/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal, Show } from "solid-js"; 4 | import clickOutside from "./click-outside"; 5 | import "./style.css"; 6 | 7 | function App() { 8 | const [show, setShow] = createSignal(false); 9 | 10 | return ( 11 | setShow(true)}>Open Modal}> 12 | 15 | 16 | ); 17 | } 18 | 19 | render(() => , document.getElementById("app")); 20 | -------------------------------------------------------------------------------- /test/valid/bindings/events/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal } from "solid-js"; 4 | 5 | import "./style.css"; 6 | 7 | function App() { 8 | const [pos, setPos] = createSignal({ x: 0, y: 0 }); 9 | 10 | function handleMouseMove(event) { 11 | setPos({ 12 | x: event.clientX, 13 | y: event.clientY, 14 | }); 15 | } 16 | 17 | return ( 18 |
    19 | The mouse position is {pos().x} x {pos().y} 20 |
    21 | ); 22 | } 23 | 24 | render(() => , document.getElementById("app")); 25 | -------------------------------------------------------------------------------- /test/valid/bindings/forwarding-refs/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { onMount, onCleanup } from "solid-js"; 4 | 5 | import Canvas from "./canvas"; 6 | 7 | function App() { 8 | let canvas; 9 | onMount(() => { 10 | const ctx = canvas.getContext("2d"); 11 | let frame = requestAnimationFrame(loop); 12 | 13 | function loop(t) { 14 | frame = requestAnimationFrame(loop); 15 | 16 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 17 | 18 | for (let p = 0; p < imageData.data.length; p += 4) { 19 | const i = p / 4; 20 | const x = i % canvas.width; 21 | const y = (i / canvas.height) >>> 0; 22 | 23 | const r = 64 + (128 * x) / canvas.width + 64 * Math.sin(t / 1000); 24 | const g = 64 + (128 * y) / canvas.height + 64 * Math.cos(t / 1000); 25 | const b = 128; 26 | 27 | imageData.data[p + 0] = r; 28 | imageData.data[p + 1] = g; 29 | imageData.data[p + 2] = b; 30 | imageData.data[p + 3] = 255; 31 | } 32 | 33 | ctx.putImageData(imageData, 0, 0); 34 | } 35 | 36 | onCleanup(() => cancelAnimationFrame(frame)); 37 | }); 38 | 39 | return ; 40 | } 41 | 42 | render(() => , document.getElementById("app")); 43 | -------------------------------------------------------------------------------- /test/valid/bindings/refs/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { onMount, onCleanup } from "solid-js"; 4 | 5 | import "./style.css"; 6 | 7 | function App() { 8 | let canvas; 9 | onMount(() => { 10 | const ctx = canvas.getContext("2d"); 11 | let frame = requestAnimationFrame(loop); 12 | 13 | function loop(t) { 14 | frame = requestAnimationFrame(loop); 15 | 16 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 17 | 18 | for (let p = 0; p < imageData.data.length; p += 4) { 19 | const i = p / 4; 20 | const x = i % canvas.width; 21 | const y = (i / canvas.height) >>> 0; 22 | 23 | const r = 64 + (128 * x) / canvas.width + 64 * Math.sin(t / 1000); 24 | const g = 64 + (128 * y) / canvas.height + 64 * Math.cos(t / 1000); 25 | const b = 128; 26 | 27 | imageData.data[p + 0] = r; 28 | imageData.data[p + 1] = g; 29 | imageData.data[p + 2] = b; 30 | imageData.data[p + 3] = 255; 31 | } 32 | 33 | ctx.putImageData(imageData, 0, 0); 34 | } 35 | 36 | onCleanup(() => cancelAnimationFrame(frame)); 37 | }); 38 | 39 | return ; 40 | } 41 | 42 | render(() => , document.getElementById("app")); 43 | -------------------------------------------------------------------------------- /test/valid/bindings/spreads/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import Info from "./info"; 4 | 5 | const pkg = { 6 | name: "solid-js", 7 | version: 1, 8 | speed: "⚡️", 9 | website: "https://solidjs.com", 10 | }; 11 | 12 | function App() { 13 | return ; 14 | } 15 | 16 | render(() => , document.getElementById("app")); 17 | -------------------------------------------------------------------------------- /test/valid/bindings/style/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal } from "solid-js"; 4 | 5 | function App() { 6 | const [num, setNum] = createSignal(0); 7 | setInterval(() => setNum((num() + 1) % 255), 30); 8 | 9 | return ( 10 |
    17 | Some Text 18 |
    19 | ); 20 | } 21 | 22 | render(() => , document.getElementById("app")); 23 | -------------------------------------------------------------------------------- /test/valid/control-flow/dynamic/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render, Dynamic } from "solid-js/web"; 3 | import { createSignal, For } from "solid-js"; 4 | 5 | const RedThing = () => Red Thing; 6 | const GreenThing = () => Green Thing; 7 | const BlueThing = () => Blue Thing; 8 | 9 | const options = { 10 | red: RedThing, 11 | green: GreenThing, 12 | blue: BlueThing, 13 | }; 14 | 15 | function App() { 16 | const [selected, setSelected] = createSignal("red"); 17 | 18 | return ( 19 | <> 20 | 23 | 24 | 25 | ); 26 | } 27 | 28 | render(() => , document.getElementById("app")); 29 | -------------------------------------------------------------------------------- /test/valid/control-flow/error-boundary/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { ErrorBoundary } from "solid-js"; 4 | 5 | const Broken = () => { 6 | throw new Error("Oh No"); 7 | return <>Never Getting Here; 8 | }; 9 | 10 | function App() { 11 | return ( 12 | <> 13 |
    Before
    14 | err}> 15 | 16 | 17 |
    After
    18 | 19 | ); 20 | } 21 | 22 | render(() => , document.getElementById("app")); 23 | -------------------------------------------------------------------------------- /test/valid/control-flow/for/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal, For } from "solid-js"; 4 | 5 | function App() { 6 | const [cats] = createSignal([ 7 | { id: "J---aiyznGQ", name: "Keyboard Cat" }, 8 | { id: "z_AbfPXTKms", name: "Maru" }, 9 | { id: "OUtn3pvWmpg", name: "Henri The Existential Cat" }, 10 | ]); 11 | 12 | return ( 13 | 24 | ); 25 | } 26 | 27 | render(() => , document.getElementById("app")); 28 | -------------------------------------------------------------------------------- /test/valid/control-flow/index/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal, Index } from "solid-js"; 4 | 5 | function App() { 6 | const [cats] = createSignal([ 7 | { id: "J---aiyznGQ", name: "Keyboard Cat" }, 8 | { id: "z_AbfPXTKms", name: "Maru" }, 9 | { id: "OUtn3pvWmpg", name: "Henri The Existential Cat" }, 10 | ]); 11 | 12 | return ( 13 | 24 | ); 25 | } 26 | 27 | render(() => , document.getElementById("app")); 28 | -------------------------------------------------------------------------------- /test/valid/control-flow/portal/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render, Portal } from "solid-js/web"; 3 | 4 | function App() { 5 | return ( 6 |
    7 |

    Just some text inside a div that has a restricted size.

    8 | 9 | 13 | 14 |
    15 | ); 16 | } 17 | 18 | render(() => , document.getElementById("app")); 19 | -------------------------------------------------------------------------------- /test/valid/control-flow/show/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal, Show } from "solid-js"; 4 | 5 | function App() { 6 | const [loggedIn, setLoggedIn] = createSignal(false); 7 | const toggle = () => setLoggedIn(!loggedIn()); 8 | 9 | return ( 10 | Log in}> 11 | 12 | 13 | ); 14 | } 15 | 16 | render(() => , document.getElementById("app")); 17 | -------------------------------------------------------------------------------- /test/valid/control-flow/switch/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal, Switch, Match } from "solid-js"; 4 | 5 | function App() { 6 | const [x] = createSignal(7); 7 | 8 | return ( 9 | {x()} is between 5 and 10

    }> 10 | 10}> 11 |

    {x()} is greater than 10

    12 |
    13 | x()}> 14 |

    {x()} is less than 5

    15 |
    16 |
    17 | ); 18 | } 19 | 20 | render(() => , document.getElementById("app")); 21 | -------------------------------------------------------------------------------- /test/valid/examples/async-resource.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { createSignal, createResource } from "solid-js"; 3 | import { render } from "solid-js/web"; 4 | 5 | const fetchUser = async (id) => (await fetch(`https://swapi.dev/api/people/${id}/`)).json(); 6 | 7 | const App = () => { 8 | const [userId, setUserId] = createSignal(); 9 | const [user] = createResource(userId, fetchUser); 10 | 11 | return ( 12 | <> 13 | setUserId(e.currentTarget.value)} 18 | /> 19 | {user.loading && "Loading..."} 20 |
    21 |
    {JSON.stringify(user(), null, 2)}
    22 |
    23 | 24 | ); 25 | }; 26 | 27 | render(App, document.getElementById("app")); 28 | -------------------------------------------------------------------------------- /test/valid/examples/counter.jsx: -------------------------------------------------------------------------------- 1 | import { createSignal, onCleanup } from "solid-js"; 2 | import { render } from "solid-js/web"; 3 | 4 | const CountingComponent = () => { 5 | const [count, setCount] = createSignal(0); 6 | const interval = setInterval(() => setCount((c) => c + 1), 1000); 7 | onCleanup(function cleanup() { 8 | clearInterval(interval + count()); 9 | }); 10 | return
    Count value is {count()}
    ; 11 | }; 12 | 13 | render(() => , document.getElementById("app")); 14 | -------------------------------------------------------------------------------- /test/valid/examples/css-animations.jsx: -------------------------------------------------------------------------------- 1 | import { createSignal, For, Match, Switch } from "solid-js"; 2 | import { render } from "solid-js/web"; 3 | import { Transition, TransitionGroup } from "solid-transition-group"; 4 | import "./styles.css"; 5 | 6 | function shuffle(array) { 7 | return array.sort(() => Math.random() - 0.5); 8 | } 9 | let nextId = 10; 10 | 11 | const App = () => { 12 | const [show, toggleShow] = createSignal(true), 13 | [select, setSelect] = createSignal(0), 14 | [numList, setNumList] = createSignal([1, 2, 3, 4, 5, 6, 7, 8, 9]), 15 | randomIndex = () => Math.floor(Math.random() * numList().length); 16 | 17 | return ( 18 | <> 19 | 20 |
    21 | Transition: 22 | 23 | {show() && ( 24 |
    25 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero, 26 | at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus. 27 |
    28 | )} 29 |
    30 |
    31 | Animation: 32 | 33 | {show() && ( 34 |
    35 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero, 36 | at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus. 37 |
    38 | )} 39 |
    40 |
    41 | Custom JS: 42 | { 44 | const a = el.animate([{ opacity: 0 }, { opacity: 1 }], { 45 | duration: 600, 46 | }); 47 | a.finished.then(done); 48 | }} 49 | onExit={(el, done) => { 50 | const a = el.animate([{ opacity: 1 }, { opacity: 0 }], { 51 | duration: 600, 52 | }); 53 | a.finished.then(done); 54 | }} 55 | > 56 | {show() && ( 57 |
    58 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero, 59 | at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus. 60 |
    61 | )} 62 |
    63 |
    64 | Switch OutIn 65 |
    66 | 67 | 68 | 69 | 70 |

    The First

    71 |
    72 | 73 |

    The Second

    74 |
    75 | 76 |

    The Third

    77 |
    78 |
    79 |
    80 | Group 81 |
    82 | 91 | 100 | 108 |
    109 | 110 | {(r) => {r}} 111 | 112 | 113 | ); 114 | }; 115 | 116 | render(App, document.getElementById("app")); 117 | -------------------------------------------------------------------------------- /test/valid/examples/familiar-and-modern.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { onCleanup, createSignal } from "solid-js"; 4 | 5 | const CountingComponent = () => { 6 | const [count, setCount] = createSignal(0); 7 | const interval = setInterval(() => setCount((count) => count + 1), 1000); 8 | onCleanup(() => clearInterval(interval)); 9 | return
    Count value is {count()}
    ; 10 | }; 11 | 12 | render(() => , document.getElementById("app")); 13 | -------------------------------------------------------------------------------- /test/valid/examples/formvalidation-main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createStore } from "solid-js/store"; 4 | import { useForm } from "./validation"; 5 | import "./styles.css"; 6 | 7 | const EMAILS = ["johnsmith@outlook.com", "mary@gmail.com", "djacobs@move.org"]; 8 | 9 | function fetchUserName(name) { 10 | return new Promise((resolve) => { 11 | setTimeout(() => resolve(EMAILS.indexOf(name) > -1), 200); 12 | }); 13 | } 14 | 15 | const ErrorMessage = (props) => {props.error}; 16 | 17 | const App = () => { 18 | const { validate, formSubmit, errors } = useForm({ 19 | errorClass: "error-input", 20 | }); 21 | const [fields, setFields] = createStore(); 22 | const fn = (form) => { 23 | form.submit(); 24 | console.log("Done"); 25 | }; 26 | const userNameExists = async ({ value }) => { 27 | const exists = await fetchUserName(value); 28 | return exists && `${value} is already being used`; 29 | }; 30 | const matchesPassword = ({ value }) => 31 | value === fields.password ? false : "Passwords must Match"; 32 | 33 | return ( 34 |
    35 |

    Sign Up

    36 |
    37 | 44 | {errors.email && } 45 |
    46 |
    47 | setFields("password", e.target.value)} 54 | use:validate 55 | /> 56 | {errors.password && } 57 |
    58 |
    59 | 66 | {errors.confirmpassword && } 67 |
    68 | 69 | 70 |
    71 | ); 72 | }; 73 | 74 | render(App, document.getElementById("app")); 75 | -------------------------------------------------------------------------------- /test/valid/examples/formvalidation-validation.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { createStore } from "solid-js/store"; 3 | 4 | function checkValid({ element, validators = [] }, setErrors, errorClass) { 5 | return async () => { 6 | element.setCustomValidity(""); 7 | element.checkValidity(); 8 | let message = element.validationMessage; 9 | if (!message) { 10 | for (const validator of validators) { 11 | const text = await validator(element); 12 | if (text) { 13 | element.setCustomValidity(text); 14 | break; 15 | } 16 | } 17 | message = element.validationMessage; 18 | } 19 | if (message) { 20 | errorClass && element.classList.toggle(errorClass, true); 21 | setErrors({ [element.name]: message }); 22 | } 23 | }; 24 | } 25 | 26 | export function useForm({ errorClass }) { 27 | const [errors, setErrors] = createStore({}), 28 | fields = {}; 29 | 30 | const validate = (ref, accessor) => { 31 | const validators = accessor() || []; 32 | let config; 33 | fields[ref.name] = config = { element: ref, validators }; 34 | ref.onblur = checkValid(config, setErrors, errorClass); 35 | ref.oninput = () => { 36 | if (!errors[ref.name]) return; 37 | setErrors({ [ref.name]: undefined }); 38 | errorClass && ref.classList.toggle(errorClass, false); 39 | }; 40 | }; 41 | 42 | const formSubmit = (ref, accessor) => { 43 | const callback = accessor() || (() => {}); 44 | ref.setAttribute("novalidate", ""); 45 | ref.onsubmit = async (e) => { 46 | e.preventDefault(); 47 | let errored = false; 48 | 49 | for (const k in fields) { 50 | const field = fields[k]; 51 | await checkValid(field, setErrors, errorClass)(); 52 | if (!errored && field.element.validationMessage) { 53 | field.element.focus(); 54 | errored = true; 55 | } 56 | } 57 | !errored && callback(ref); 58 | }; 59 | }; 60 | 61 | return { validate, formSubmit, errors }; 62 | } 63 | a; 64 | -------------------------------------------------------------------------------- /test/valid/examples/introduction-signals.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal } from "solid-js"; 4 | 5 | function Counter() { 6 | const [count] = createSignal(0); 7 | 8 | setTimeout(() => console.log(count()), 4500); 9 | 10 | return
    Count: {count()}
    ; 11 | } 12 | 13 | render(() => , document.getElementById("app")); 14 | -------------------------------------------------------------------------------- /test/valid/examples/simple-todos.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { createEffect, For } from "solid-js"; 3 | import { createStore, SetStoreFunction, Store } from "solid-js/store"; 4 | import { render } from "solid-js/web"; 5 | 6 | // Checked but not used for demo purposes 7 | function createLocalStore(initState: T): [Store, SetStoreFunction] { 8 | const [state, setState] = createStore(initState); 9 | if (localStorage.todos) setState(JSON.parse(localStorage.todos)); 10 | createEffect(() => (localStorage.todos = JSON.stringify(state))); 11 | return [state, setState]; 12 | } 13 | 14 | const App = () => { 15 | const [state, setState] = createStore({ 16 | todos: [], 17 | newTitle: "", 18 | }); 19 | return ( 20 | <> 21 |

    Simple Todos Example

    22 | setState({ newTitle: e.target.value })} 27 | /> 28 | 44 | 45 | {(todo, i) => { 46 | const { done, title } = todo; 47 | return ( 48 |
    49 | setState("todos", i(), { done: e.target.checked })} 53 | /> 54 | setState("todos", i(), { title: e.target.value })} 58 | /> 59 | 67 |
    68 | ); 69 | }} 70 |
    71 | 72 | ); 73 | }; 74 | 75 | render(App, document.getElementById("app")); 76 | -------------------------------------------------------------------------------- /test/valid/examples/suspense-transitions-child.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { createResource } from "solid-js"; 3 | 4 | const CONTENT = { 5 | Uno: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`, 6 | Dos: `Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?`, 7 | Tres: `On the other hand, we denounce with righteous indignation and dislike men who are so beguiled and demoralized by the charms of pleasure of the moment, so blinded by desire, that they cannot foresee the pain and trouble that are bound to ensue; and equal blame belongs to those who fail in their duty through weakness of will, which is the same as saying through shrinking from toil and pain. These cases are perfectly simple and easy to distinguish. In a free hour, when our power of choice is untrammelled and when nothing prevents our being able to do what we like best, every pleasure is to be welcomed and every pain avoided. But in certain circumstances and owing to the claims of duty or the obligations of business it will frequently occur that pleasures have to be repudiated and annoyances accepted. The wise man therefore always holds in these matters to this principle of selection: he rejects pleasures to secure other greater pleasures, or else he endures pains to avoid worse pains.`, 8 | }; 9 | 10 | function createDelay() { 11 | return new Promise((resolve) => { 12 | const delay = Math.random() * 420 + 160; 13 | setTimeout(() => resolve(delay), delay); 14 | }); 15 | } 16 | 17 | const Child = (props) => { 18 | const [time] = createResource(createDelay); 19 | 20 | return ( 21 |
    22 | This content is for page "{props.page}" after {time()?.toFixed()}ms. 23 |

    {CONTENT[props.page]}

    24 |
    25 | ); 26 | }; 27 | 28 | export default Child; 29 | -------------------------------------------------------------------------------- /test/valid/examples/suspense-transitions-main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { createSignal, Suspense, Switch, Match, useTransition } from "solid-js"; 3 | import { render } from "solid-js/web"; 4 | import Child from "./child"; 5 | 6 | import "./styles.css"; 7 | 8 | const App = () => { 9 | const [tab, setTab] = createSignal(0); 10 | const [pending, start] = useTransition(); 11 | const updateTab = (index) => () => start(() => setTab(index)); 12 | 13 | return ( 14 | <> 15 |
      16 |
    • 17 | Uno 18 |
    • 19 |
    • 20 | Dos 21 |
    • 22 |
    • 23 | Tres 24 |
    • 25 |
    26 |
    27 | Loading...
    }> 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
    41 | 42 | ); 43 | }; 44 | 45 | render(App, document.getElementById("app")); 46 | -------------------------------------------------------------------------------- /test/valid/introduction/basics/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | 4 | function HelloWorld() { 5 | return
    Hello Solid World!
    ; 6 | } 7 | 8 | render(() => , document.getElementById("app")); 9 | -------------------------------------------------------------------------------- /test/valid/introduction/components/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import Nested from "./nested"; 4 | 5 | function App() { 6 | return ( 7 | <> 8 |

    This is a Header

    9 | 10 | 11 | ); 12 | } 13 | 14 | render(() => , document.getElementById("app")); 15 | -------------------------------------------------------------------------------- /test/valid/introduction/components/nested.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | export default () =>

    This is a Paragraph

    ; 3 | -------------------------------------------------------------------------------- /test/valid/introduction/derived-signals/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal } from "solid-js"; 4 | 5 | function Counter() { 6 | const [count, setCount] = createSignal(0); 7 | const doubleCount = () => count() * 2; 8 | 9 | setInterval(() => setCount(count() + 1), 1000); 10 | 11 | return
    Count: {doubleCount()}
    ; 12 | } 13 | 14 | render(() => , document.getElementById("app")); 15 | -------------------------------------------------------------------------------- /test/valid/introduction/effects/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal, createEffect } from "solid-js"; 4 | 5 | function Counter() { 6 | const [count, setCount] = createSignal(0); 7 | createEffect(() => { 8 | console.log("The count is now", count()); 9 | }); 10 | 11 | return ; 12 | } 13 | 14 | render(() => , document.getElementById("app")); 15 | -------------------------------------------------------------------------------- /test/valid/introduction/jsx/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | 4 | function HelloWorld() { 5 | const name = "Solid"; 6 | const svg = ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Sorry but this browser does not support inline SVG. 16 | 17 | ); 18 | 19 | return ( 20 | <> 21 |
    Hello {name}!
    22 | {svg} 23 | 24 | ); 25 | } 26 | 27 | render(() => , document.getElementById("app")); 28 | -------------------------------------------------------------------------------- /test/valid/introduction/memos/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal, createMemo } from "solid-js"; 4 | 5 | function fibonacci(num) { 6 | if (num <= 1) return 1; 7 | 8 | return fibonacci(num - 1) + fibonacci(num - 2); 9 | } 10 | 11 | function Counter() { 12 | const [count, setCount] = createSignal(10); 13 | const fib = createMemo(() => fibonacci(count())); 14 | 15 | return ( 16 | <> 17 | 18 |
    19 | 1. {fib()} {fib()} {fib()} {fib()} {fib()} 20 |
    21 |
    22 | 2. {fib()} {fib()} {fib()} {fib()} {fib()} 23 |
    24 |
    25 | 3. {fib()} {fib()} {fib()} {fib()} {fib()} 26 |
    27 |
    28 | 4. {fib()} {fib()} {fib()} {fib()} {fib()} 29 |
    30 |
    31 | 5. {fib()} {fib()} {fib()} {fib()} {fib()} 32 |
    33 |
    34 | 6. {fib()} {fib()} {fib()} {fib()} {fib()} 35 |
    36 |
    37 | 7. {fib()} {fib()} {fib()} {fib()} {fib()} 38 |
    39 |
    40 | 8. {fib()} {fib()} {fib()} {fib()} {fib()} 41 |
    42 |
    43 | 9. {fib()} {fib()} {fib()} {fib()} {fib()} 44 |
    45 |
    46 | 10. {fib()} {fib()} {fib()} {fib()} {fib()} 47 |
    48 | 49 | ); 50 | } 51 | 52 | render(() => , document.getElementById("app")); 53 | -------------------------------------------------------------------------------- /test/valid/introduction/signals/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal } from "solid-js"; 4 | 5 | function Counter() { 6 | const [count, setCount] = createSignal(0); 7 | 8 | setInterval(() => setCount(count() + 1), 1000); 9 | 10 | return
    Count: {count()}
    ; 11 | } 12 | 13 | render(() => , document.getElementById("app")); 14 | -------------------------------------------------------------------------------- /test/valid/lifecycles/onCleanup/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal, onCleanup } from "solid-js"; 4 | 5 | function Counter() { 6 | const [count, setCount] = createSignal(0); 7 | 8 | const timer = setInterval(() => setCount(count() + 1), 1000); 9 | onCleanup(() => clearInterval(timer)); 10 | 11 | return
    Count: {count()}
    ; 12 | } 13 | 14 | render(() => , document.getElementById("app")); 15 | -------------------------------------------------------------------------------- /test/valid/lifecycles/onMount/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal, onMount, For } from "solid-js"; 4 | import "./styles.css"; 5 | 6 | function App() { 7 | const [photos, setPhotos] = createSignal([]); 8 | 9 | onMount(async () => { 10 | const res = await fetch(`https://jsonplaceholder.typicode.com/photos?_limit=20`); 11 | setPhotos(await res.json()); 12 | }); 13 | 14 | return ( 15 | <> 16 |

    Photo album

    17 | 18 |
    19 | Loading...

    }> 20 | {(photo) => ( 21 |
    22 | {photo.title} 23 |
    {photo.title}
    24 |
    25 | )} 26 |
    27 |
    28 | 29 | ); 30 | } 31 | 32 | render(() => , document.getElementById("app")); 33 | -------------------------------------------------------------------------------- /test/valid/props/children/colored-list.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { createEffect, children } from "solid-js"; 3 | 4 | export default function ColoredList(props) { 5 | const c = children(() => props.children); 6 | createEffect(() => c().forEach((item) => (item.style.color = props.color))); 7 | return <>{c()}; 8 | } 9 | -------------------------------------------------------------------------------- /test/valid/props/children/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal, For } from "solid-js"; 4 | 5 | import ColoredList from "./colored-list"; 6 | 7 | function App() { 8 | const [color, setColor] = createSignal("purple"); 9 | 10 | return ( 11 | <> 12 | 13 | {(item) =>
    {item}
    }
    14 |
    15 | 16 | 17 | ); 18 | } 19 | 20 | render(() => , document.getElementById("app")); 21 | -------------------------------------------------------------------------------- /test/valid/props/default-props/greeting.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { mergeProps } from "solid-js"; 3 | 4 | export default function Greeting(props) { 5 | const merged = mergeProps({ greeting: "Hi", name: "John" }, props); 6 | 7 | return ( 8 |

    9 | {merged.greeting} {merged.name} 10 |

    11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /test/valid/props/default-props/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal } from "solid-js"; 4 | 5 | import Greeting from "./greeting"; 6 | 7 | function App() { 8 | const [name, setName] = createSignal(); 9 | 10 | return ( 11 | <> 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | } 19 | 20 | render(() => , document.getElementById("app")); 21 | -------------------------------------------------------------------------------- /test/valid/props/splitting-props/greeting.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { splitProps } from "solid-js"; 3 | 4 | export default function Greeting(props) { 5 | const [local, others] = splitProps(props, ["greeting", "name"]); 6 | return ( 7 |

    8 | {local.greeting} {local.name} 9 |

    10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /test/valid/props/splitting-props/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal } from "solid-js"; 4 | 5 | import Greeting from "./greeting"; 6 | 7 | function App() { 8 | const [name, setName] = createSignal("Jakob"); 9 | 10 | return ( 11 | <> 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | render(() => , document.getElementById("app")); 19 | -------------------------------------------------------------------------------- /test/valid/reactivity/batching-updates/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal, batch } from "solid-js"; 4 | 5 | const App = () => { 6 | const [firstName, setFirstName] = createSignal("John"); 7 | const [lastName, setLastName] = createSignal("Smith"); 8 | const fullName = () => { 9 | console.log("Running FullName"); 10 | return `${firstName()} ${lastName()}`; 11 | }; 12 | const updateNames = () => { 13 | console.log("Button Clicked"); 14 | batch(() => { 15 | setFirstName(firstName() + "n"); 16 | setLastName(lastName() + "!"); 17 | }); 18 | }; 19 | 20 | return ; 21 | }; 22 | 23 | render(App, document.getElementById("app")); 24 | -------------------------------------------------------------------------------- /test/valid/reactivity/on/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal, createEffect, on } from "solid-js"; 4 | 5 | const App = () => { 6 | const [a, setA] = createSignal(1); 7 | const [b, setB] = createSignal(1); 8 | const [c, setC] = createSignal(1); 9 | 10 | createEffect( 11 | on( 12 | a, 13 | (a) => { 14 | console.log(a, b()); 15 | }, 16 | { defer: true } 17 | ) 18 | ); 19 | 20 | return ( 21 | <> 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | }; 31 | 32 | render(App, document.getElementById("app")); 33 | -------------------------------------------------------------------------------- /test/valid/reactivity/untrack/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { createSignal, createEffect, untrack } from "solid-js"; 4 | 5 | const App = () => { 6 | const [a, setA] = createSignal(1); 7 | const [b, setB] = createSignal(1); 8 | 9 | createEffect(() => { 10 | console.log(a(), untrack(b)); 11 | }); 12 | 13 | return ( 14 | <> 15 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | render(App, document.getElementById("app")); 22 | -------------------------------------------------------------------------------- /test/valid/stores/context/counter.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { createSignal, createContext, useContext } from "solid-js"; 3 | 4 | const CounterContext = createContext(); 5 | 6 | export function CounterProvider(props) { 7 | // eslint-disable-next-line solid/reactivity 8 | const [count, setCount] = createSignal(props.count || 0), 9 | store = [ 10 | count, 11 | { 12 | increment() { 13 | setCount((c) => c + 1); 14 | }, 15 | decrement() { 16 | setCount((c) => c - 1); 17 | }, 18 | }, 19 | ]; 20 | return {props.children}; 21 | } 22 | 23 | export function useCounter() { 24 | return useContext(CounterContext); 25 | } 26 | -------------------------------------------------------------------------------- /test/valid/stores/context/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import Nested from "./nested"; 4 | import { CounterProvider } from "./counter"; 5 | 6 | function App() { 7 | return ( 8 | <> 9 |

    Welcome to Counter App

    10 | 11 | 12 | ); 13 | } 14 | 15 | render( 16 | () => ( 17 | 18 | 19 | 20 | ), 21 | document.getElementById("app") 22 | ); 23 | -------------------------------------------------------------------------------- /test/valid/stores/context/nested.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { useCounter } from "./counter"; 3 | 4 | export default function Nested() { 5 | const [count, { increment, decrement }] = useCounter(); 6 | return ( 7 | <> 8 |
    {count()}
    9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /test/valid/stores/create-store/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { For } from "solid-js"; 4 | import { createStore } from "solid-js/store"; 5 | 6 | const App = () => { 7 | let input; 8 | let todoId = 0; 9 | const [store, setStore] = createStore({ todos: [] }); 10 | const addTodo = (text) => { 11 | setStore("todos", (todos) => [...todos, { id: ++todoId, text, completed: false }]); 12 | }; 13 | const toggleTodo = (id) => { 14 | setStore( 15 | "todos", 16 | (todo) => todo.id === id, 17 | "completed", 18 | (completed) => !completed 19 | ); 20 | }; 21 | 22 | return ( 23 | <> 24 |
    25 | 26 | 35 |
    36 | 37 | {(todo) => { 38 | const { id, text } = todo; 39 | console.log(`Creating ${text}`); 40 | return ( 41 |
    42 | 43 | 44 | {text} 45 | 46 |
    47 | ); 48 | }} 49 |
    50 | 51 | ); 52 | }; 53 | 54 | render(App, document.getElementById("app")); 55 | -------------------------------------------------------------------------------- /test/valid/stores/immutable-stores/actions.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | let nextTodoId = 0; 3 | export default { 4 | addTodo: (text) => ({ type: "ADD_TODO", id: ++nextTodoId, text }), 5 | toggleTodo: (id) => ({ type: "TOGGLE_TODO", id }), 6 | }; 7 | -------------------------------------------------------------------------------- /test/valid/stores/immutable-stores/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { For } from "solid-js"; 4 | 5 | import useRedux from "./useRedux"; 6 | import reduxStore from "./store"; 7 | import actions from "./actions"; 8 | 9 | const App = () => { 10 | const [store, { addTodo, toggleTodo }] = useRedux(reduxStore, actions); 11 | let input; 12 | return ( 13 | <> 14 |
    15 | 16 | 25 |
    26 | 27 | {(todo) => { 28 | const { id, text } = todo; 29 | console.log("Create", text); 30 | return ( 31 |
    32 | 33 | 34 | {text} 35 | 36 |
    37 | ); 38 | }} 39 |
    40 | 41 | ); 42 | }; 43 | 44 | render(App, document.getElementById("app")); 45 | -------------------------------------------------------------------------------- /test/valid/stores/immutable-stores/store.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { createStore } from "redux"; 3 | 4 | // todos reducer 5 | const todos = (state = { todos: [] }, action) => { 6 | switch (action.type) { 7 | case "ADD_TODO": 8 | return { 9 | todos: [ 10 | ...state.todos, 11 | { 12 | id: action.id, 13 | text: action.text, 14 | completed: false, 15 | }, 16 | ], 17 | }; 18 | case "TOGGLE_TODO": 19 | return { 20 | todos: state.todos.map((todo) => 21 | todo.id === action.id ? { ...todo, completed: !todo.completed } : todo 22 | ), 23 | }; 24 | default: 25 | return state; 26 | } 27 | }; 28 | 29 | const [store, setStore] = createStore(todos); 30 | export default [store, setStore]; 31 | -------------------------------------------------------------------------------- /test/valid/stores/immutable-stores/useRedux.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { onCleanup } from "solid-js"; 3 | import { createStore, reconcile } from "solid-js/store"; 4 | 5 | export default function useRedux(store, actions) { 6 | const [state, setState] = createStore(store.getState()); 7 | const unsubscribe = store.subscribe(() => setState(reconcile(store.getState()))); 8 | onCleanup(() => unsubscribe()); 9 | return [state, mapActions(store, actions)]; 10 | } 11 | 12 | function mapActions(store, actions) { 13 | const mapped = {}; 14 | for (const key in actions) { 15 | mapped[key] = (...args) => store.dispatch(actions[key](...args)); 16 | } 17 | return mapped; 18 | } 19 | -------------------------------------------------------------------------------- /test/valid/stores/mutation/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { For } from "solid-js"; 3 | import { render } from "solid-js/web"; 4 | import { createStore, produce } from "solid-js/store"; 5 | 6 | const App = () => { 7 | let input; 8 | let todoId = 0; 9 | const [store, setStore] = createStore({ todos: [] }); 10 | const addTodo = (text) => { 11 | setStore( 12 | "todos", 13 | produce((todos) => { 14 | todos.push({ id: ++todoId, text, completed: false }); 15 | }) 16 | ); 17 | }; 18 | const toggleTodo = (id) => { 19 | setStore( 20 | "todos", 21 | (todo) => todo.id === id, 22 | produce((todo) => (todo.completed = !todo.completed)) 23 | ); 24 | }; 25 | 26 | return ( 27 | <> 28 |
    29 | 30 | 39 |
    40 | 41 | {(todo) => { 42 | const { id, text } = todo; 43 | console.log(`Creating ${text}`); 44 | return ( 45 |
    46 | 47 | 48 | {text} 49 | 50 |
    51 | ); 52 | }} 53 |
    54 | 55 | ); 56 | }; 57 | 58 | render(App, document.getElementById("app")); 59 | -------------------------------------------------------------------------------- /test/valid/stores/nested-reactivity/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import { For, createSignal } from "solid-js"; 4 | 5 | const App = () => { 6 | const [todos, setTodos] = createSignal([]); 7 | let input; 8 | let todoId = 0; 9 | 10 | const addTodo = (text) => { 11 | const [completed, setCompleted] = createSignal(false); 12 | setTodos([...todos(), { id: ++todoId, text, completed, setCompleted }]); 13 | }; 14 | const toggleTodo = (id) => { 15 | const index = todos().findIndex((t) => t.id === id); 16 | const todo = todos()[index]; 17 | if (todo) todo.setCompleted(!todo.completed()); 18 | }; 19 | 20 | return ( 21 | <> 22 |
    23 | 24 | 33 |
    34 | 35 | {(todo) => { 36 | const { id, text } = todo; 37 | console.log(`Creating ${text}`); 38 | return ( 39 |
    40 | 41 | 42 | {text} 43 | 44 |
    45 | ); 46 | }} 47 |
    48 | 49 | ); 50 | }; 51 | 52 | render(App, document.getElementById("app")); 53 | -------------------------------------------------------------------------------- /test/valid/stores/without-context/counter.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { createSignal, createMemo, createRoot } from "solid-js"; 3 | 4 | function createCounter() { 5 | const [count, setCount] = createSignal(0); 6 | const increment = () => setCount(count() + 1); 7 | const doubleCount = createMemo(() => count() * 2); 8 | return { count, doubleCount, increment }; 9 | } 10 | 11 | export default createRoot(createCounter); 12 | -------------------------------------------------------------------------------- /test/valid/stores/without-context/main.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render } from "solid-js/web"; 3 | import counter from "./counter"; 4 | 5 | function Counter() { 6 | const { count, doubleCount, increment } = counter; 7 | 8 | return ( 9 | 12 | ); 13 | } 14 | 15 | render(() => , document.getElementById("app")); 16 | -------------------------------------------------------------------------------- /test/vitest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | test: { 3 | globals: true, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@tsconfig/node20/tsconfig.json", 3 | "compilerOptions": { 4 | "forceConsistentCasingInFileNames": true, 5 | "isolatedModules": true, 6 | "noEmit": true, 7 | "allowJs": true, 8 | "resolveJsonModule": true, 9 | "allowImportingTsExtensions": true 10 | }, 11 | "include": ["packages/eslint-plugin-solid", "packages/eslint-solid-standalone", "test", "scripts"], 12 | "exclude": ["**/dist", "**/dist.*"] 13 | } 14 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "tasks": { 4 | "turbo:build": { 5 | "dependsOn": ["^turbo:build"], 6 | "outputs": ["dist/**", "dist.*"] 7 | }, 8 | "turbo:test": { 9 | "dependsOn": ["turbo:build"], 10 | "env": ["PARSER"] 11 | }, 12 | "turbo:docs": { 13 | "env": ["PARSER"] 14 | }, 15 | "//#turbo:docs": { 16 | "dependsOn": ["eslint-plugin-solid#turbo:docs"] 17 | }, 18 | "//#turbo:lint": { 19 | "dependsOn": ["//#transit"] 20 | }, 21 | "//#turbo:tsc": { 22 | "dependsOn": [ 23 | "eslint-plugin-solid#turbo:build", 24 | "eslint-solid-standalone#turbo:build", 25 | "//#transit" 26 | ] 27 | }, 28 | "//#transit": { 29 | "dependsOn": ["^transit"] 30 | } 31 | } 32 | } 33 | --------------------------------------------------------------------------------