├── .clippy.toml ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── blank_issue.md │ └── bug_report.md └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .rustfmt.toml ├── .vscode └── settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── benchmarks ├── .eslintrc.js ├── .gitignore ├── benchmarks.ts ├── package-lock.json └── package.json ├── deno.json ├── examples └── dlint │ ├── config.rs │ ├── diagnostics.rs │ ├── example_config.json │ ├── main.rs │ ├── rules.rs │ ├── testdata │ ├── issue1145_no_trailing_newline.out │ ├── issue1145_no_trailing_newline.ts │ ├── simple.out │ └── simple.ts │ └── visitor.js ├── rust-toolchain.toml ├── schemas ├── rules.v1.json └── tags.v1.json ├── src ├── ast_parser.rs ├── context.rs ├── control_flow │ ├── analyze_test.rs │ └── mod.rs ├── diagnostic.rs ├── globals.rs ├── handler.rs ├── ignore_directives.rs ├── js_regex │ ├── mod.rs │ ├── reader.rs │ ├── unicode.rs │ └── validator.rs ├── lib.rs ├── linter.rs ├── performance_mark.rs ├── rules.rs ├── rules │ ├── adjacent_overload_signatures.rs │ ├── ban_ts_comment.rs │ ├── ban_types.rs │ ├── ban_unknown_rule_code.rs │ ├── ban_untagged_ignore.rs │ ├── ban_untagged_todo.rs │ ├── ban_unused_ignore.rs │ ├── camelcase.rs │ ├── constructor_super.rs │ ├── default_param_last.rs │ ├── eqeqeq.rs │ ├── explicit_function_return_type.rs │ ├── explicit_module_boundary_types.rs │ ├── for_direction.rs │ ├── fresh_handler_export.rs │ ├── fresh_server_event_handlers.rs │ ├── getter_return.rs │ ├── guard_for_in.rs │ ├── jsx_boolean_value.rs │ ├── jsx_button_has_type.rs │ ├── jsx_curly_braces.rs │ ├── jsx_key.rs │ ├── jsx_no_children_prop.rs │ ├── jsx_no_comment_text_nodes.rs │ ├── jsx_no_duplicate_props.rs │ ├── jsx_no_unescaped_entities.rs │ ├── jsx_no_useless_fragment.rs │ ├── jsx_props_no_spread_multi.rs │ ├── jsx_void_dom_elements_no_children.rs │ ├── no_array_constructor.rs │ ├── no_async_promise_executor.rs │ ├── no_await_in_loop.rs │ ├── no_await_in_sync_fn.rs │ ├── no_boolean_literal_for_arguments.rs │ ├── no_case_declarations.rs │ ├── no_class_assign.rs │ ├── no_compare_neg_zero.rs │ ├── no_cond_assign.rs │ ├── no_console.rs │ ├── no_const_assign.rs │ ├── no_constant_condition.rs │ ├── no_control_regex.rs │ ├── no_debugger.rs │ ├── no_delete_var.rs │ ├── no_deprecated_deno_api.rs │ ├── no_dupe_args.rs │ ├── no_dupe_class_members.rs │ ├── no_dupe_else_if.rs │ ├── no_dupe_keys.rs │ ├── no_duplicate_case.rs │ ├── no_empty.rs │ ├── no_empty_character_class.rs │ ├── no_empty_enum.rs │ ├── no_empty_interface.rs │ ├── no_empty_pattern.rs │ ├── no_eval.rs │ ├── no_ex_assign.rs │ ├── no_explicit_any.rs │ ├── no_external_imports.rs │ ├── no_extra_boolean_cast.rs │ ├── no_extra_non_null_assertion.rs │ ├── no_fallthrough.rs │ ├── no_func_assign.rs │ ├── no_global_assign.rs │ ├── no_implicit_declare_namespace_export.rs │ ├── no_import_assertions.rs │ ├── no_import_assign.rs │ ├── no_inferrable_types.rs │ ├── no_inner_declarations.rs │ ├── no_invalid_regexp.rs │ ├── no_invalid_triple_slash_reference.rs │ ├── no_irregular_whitespace.rs │ ├── no_misused_new.rs │ ├── no_namespace.rs │ ├── no_new_symbol.rs │ ├── no_node_globals.rs │ ├── no_non_null_asserted_optional_chain.rs │ ├── no_non_null_assertion.rs │ ├── no_obj_calls.rs │ ├── no_octal.rs │ ├── no_process_global.rs │ ├── no_prototype_builtins.rs │ ├── no_redeclare.rs │ ├── no_regex_spaces.rs │ ├── no_self_assign.rs │ ├── no_self_compare.rs │ ├── no_setter_return.rs │ ├── no_shadow_restricted_names.rs │ ├── no_sparse_arrays.rs │ ├── no_sync_fn_in_async_fn.rs │ ├── no_this_alias.rs │ ├── no_this_before_super.rs │ ├── no_throw_literal.rs │ ├── no_top_level_await.rs │ ├── no_undef.rs │ ├── no_unreachable.rs │ ├── no_unsafe_finally.rs │ ├── no_unsafe_negation.rs │ ├── no_unused_labels.rs │ ├── no_unused_vars.rs │ ├── no_useless_rename.rs │ ├── no_var.rs │ ├── no_window.rs │ ├── no_window_prefix.rs │ ├── no_with.rs │ ├── prefer_as_const.rs │ ├── prefer_ascii.rs │ ├── prefer_const.rs │ ├── prefer_namespace_keyword.rs │ ├── prefer_primordials.rs │ ├── react_no_danger.rs │ ├── react_no_danger_with_children.rs │ ├── react_rules_of_hooks.rs │ ├── require_await.rs │ ├── require_yield.rs │ ├── single_var_declarator.rs │ ├── triple_slash_reference.rs │ ├── use_isnan.rs │ ├── valid_typeof.rs │ └── verbatim_module_syntax.rs ├── swc_util.rs ├── tags.rs └── test_util.rs ├── tools ├── format.ts ├── generate_no_window_prefix_deny_list.ts ├── lint.ts ├── scaffold.ts └── tests │ ├── scaffold_integration_test.ts │ └── scaffold_test.ts └── www └── main.ts /.clippy.toml: -------------------------------------------------------------------------------- 1 | # Prefer using `SourcePos` from deno_ast because it abstracts 2 | # away swc's non-zero-indexed based positioning 3 | disallowed-methods = [ 4 | "swc_common::Spanned::span", 5 | ] 6 | disallowed-types = [ 7 | "swc_common::BytePos", 8 | "swc_common::Span", 9 | "swc_common::Spanned", 10 | ] 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | indent_size = 2 10 | max_line_length = 80 11 | 12 | [*.toml] 13 | indent_size = 4 14 | 15 | [*.md] 16 | max_line_length = unset 17 | 18 | [*.{lock,out}] # make editor neutral to .out and .lock files 19 | end_of_line = unset 20 | insert_final_newline = unset 21 | trim_trailing_whitespace = unset 22 | indent_style = unset 23 | indent_size = unset 24 | max_line_length = unset 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/blank_issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Blank Issue 3 | about: Create a blank issue. 4 | --- 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a bug report about a lint rule 4 | labels: bug 5 | --- 6 | 7 | 12 | 13 | ### Lint Name 14 | 15 | 16 | 17 | ### Code Snippet 18 | 19 | ```ts 20 | // put your code here 21 | ``` 22 | 23 | ### Expected Result 24 | 25 | ### Actual Result 26 | 27 | ### Additional Info 28 | 29 | ### Version 30 | 31 | 38 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: write 7 | 8 | jobs: 9 | rust: 10 | name: deno_lint-${{ matrix.os }} 11 | if: | 12 | github.event_name == 'push' || 13 | !startsWith(github.event.pull_request.head.label, 'denoland:') 14 | runs-on: ${{ matrix.os }} 15 | timeout-minutes: 30 16 | strategy: 17 | matrix: 18 | # macos-13 is x86_64 19 | os: [macos-13, ubuntu-latest, windows-2019] 20 | 21 | env: 22 | CARGO_INCREMENTAL: 0 23 | GH_ACTIONS: 1 24 | RUST_BACKTRACE: full 25 | RUSTFLAGS: -D warnings 26 | 27 | steps: 28 | - name: Clone repository 29 | uses: actions/checkout@v3 30 | with: 31 | submodules: true 32 | persist-credentials: false 33 | 34 | - uses: dsherret/rust-toolchain-file@v1 35 | 36 | - name: Install Deno 37 | uses: denoland/setup-deno@v1 38 | with: 39 | deno-version: v2.x 40 | 41 | - name: Install Node.js 42 | if: contains(matrix.os, 'ubuntu') 43 | uses: actions/setup-node@v3 44 | with: 45 | node-version: "18" 46 | 47 | - name: Install npm packages 48 | if: contains(matrix.os, 'ubuntu') 49 | run: npm install --ci 50 | working-directory: benchmarks 51 | 52 | - uses: Swatinem/rust-cache@v2 53 | with: 54 | save-if: ${{ github.ref == 'refs/heads/main' }} 55 | 56 | - name: Format 57 | if: contains(matrix.os, 'ubuntu') 58 | run: deno run --allow-run ./tools/format.ts --check 59 | 60 | - name: Build 61 | run: cargo build --locked --release --all-targets --all-features 62 | 63 | - name: Test 64 | run: | 65 | cargo test --locked --release --all-targets --all-features 66 | deno test --unstable --allow-read=. --allow-write=. --allow-run --allow-env ./tools 67 | 68 | - name: Lint 69 | if: contains(matrix.os, 'ubuntu') 70 | run: deno run --allow-run --allow-env ./tools/lint.ts --release 71 | 72 | - name: Benchmarks 73 | if: contains(matrix.os, 'ubuntu') 74 | run: deno run -A --quiet benchmarks/benchmarks.ts 75 | 76 | - name: Pre-release (linux) 77 | if: | 78 | contains(matrix.os, 'ubuntu') 79 | run: | 80 | cd target/release/examples 81 | zip -r dlint-x86_64-unknown-linux-gnu.zip dlint 82 | 83 | - name: Pre-release (mac) 84 | if: | 85 | contains(matrix.os, 'macOS') 86 | run: | 87 | cd target/release/examples 88 | zip -r dlint-x86_64-apple-darwin.zip dlint 89 | 90 | - name: Pre-release (windows) 91 | if: | 92 | contains(matrix.os, 'windows') 93 | run: | 94 | Compress-Archive -CompressionLevel Optimal -Force -Path target/release/examples/dlint.exe -DestinationPath target/release/examples/dlint-x86_64-pc-windows-msvc.zip 95 | 96 | - name: Release 97 | uses: softprops/action-gh-release@v1 98 | if: | 99 | github.repository == 'denoland/deno_lint' && 100 | startsWith(github.ref, 'refs/tags/') 101 | env: 102 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 103 | with: 104 | files: | 105 | target/release/examples/dlint-x86_64-pc-windows-msvc.zip 106 | target/release/examples/dlint-x86_64-unknown-linux-gnu.zip 107 | target/release/examples/dlint-x86_64-apple-darwin.zip 108 | draft: true 109 | 110 | - name: Publish 111 | if: | 112 | contains(matrix.os, 'ubuntu') && 113 | github.repository == 'denoland/deno_lint' && 114 | startsWith(github.ref, 'refs/tags/') 115 | env: 116 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 117 | run: | 118 | cargo publish -vv 119 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | releaseKind: 7 | description: "Kind of release" 8 | default: "minor" 9 | type: choice 10 | options: 11 | - patch 12 | - minor 13 | required: true 14 | 15 | jobs: 16 | rust: 17 | name: release 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 30 20 | 21 | steps: 22 | - name: Clone repository 23 | uses: actions/checkout@v3 24 | with: 25 | token: ${{ secrets.DENOBOT_PAT }} 26 | 27 | - uses: denoland/setup-deno@v1 28 | - uses: dtolnay/rust-toolchain@stable 29 | 30 | - name: Tag and release 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.DENOBOT_PAT }} 33 | GH_WORKFLOW_ACTOR: ${{ github.actor }} 34 | run: | 35 | git config user.email "denobot@users.noreply.github.com" 36 | git config user.name "denobot" 37 | deno run -A https://raw.githubusercontent.com/denoland/automation/0.16.1/tasks/publish_release.ts --${{github.event.inputs.releaseKind}} 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | /target 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "benchmarks/oak"] 2 | path = benchmarks/oak 3 | url = https://github.com/oakserver/oak.git 4 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | tab_spaces = 2 3 | edition = "2018" -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "deno.unstable": true, 5 | "deno.config": "./www/deno.json" 6 | } 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "deno_lint" 3 | version = "0.76.0" 4 | edition = "2021" 5 | description = "lint for deno" 6 | authors = ["the Deno authors"] 7 | license = "MIT" 8 | repository = "https://github.com/denoland/deno_lint" 9 | keywords = ["deno", "lint"] 10 | categories = ["development-tools"] 11 | exclude = [ 12 | "benchmarks/*", 13 | ] 14 | 15 | [lib] 16 | name = "deno_lint" 17 | 18 | [[example]] 19 | name = "dlint" 20 | test = true 21 | 22 | [features] 23 | default = [] 24 | 25 | [dependencies] 26 | deno_ast = { version = "0.48.0", features = ["scopes", "transforms", "utils", "visit", "view", "react"] } 27 | log = "0.4.20" 28 | serde = { version = "1.0.195", features = ["derive"] } 29 | serde_json = "1.0.111" 30 | regex = "1.10.2" 31 | once_cell = "1.19.0" 32 | derive_more = { version = "0.99.17", features = ["display"] } 33 | anyhow = "1.0.79" 34 | if_chain = "1.0.2" 35 | phf = { version = "0.11.2", features = ["macros"] } 36 | 37 | [dev-dependencies] 38 | ansi_term = "0.12.1" 39 | atty = "0.2.14" 40 | clap = { version = "3", features = ["cargo"] } 41 | env_logger = "0.10.1" 42 | globwalk = "0.9.1" 43 | os_pipe = "1.1.5" 44 | pulldown-cmark = "0.9.3" 45 | rayon = "1.8.0" 46 | console_static_text = "0.8.2" 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2024 the Deno authors 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 | -------------------------------------------------------------------------------- /benchmarks/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | }, 6 | extends: [], 7 | globals: { 8 | Atomics: "readonly", 9 | SharedArrayBuffer: "readonly", 10 | }, 11 | parser: "@typescript-eslint/parser", 12 | parserOptions: { 13 | ecmaVersion: 11, 14 | sourceType: "module", 15 | }, 16 | plugins: ["@typescript-eslint"], 17 | rules: { 18 | "@typescript-eslint/adjacent-overload-signatures": "warn", 19 | "@typescript-eslint/ban-ts-comment": "warn", 20 | "@typescript-eslint/ban-types": "warn", 21 | "constructor-super": "warn", 22 | "default-param-last": "warn", 23 | eqeqeq: "warn", 24 | "@typescript-eslint/explicit-function-return-type": "warn", 25 | "@typescript-eslint/explicit-module-boundary-types": "warn", 26 | "for-direction": "warn", 27 | "getter-return": "warn", 28 | "no-array-constructor": "warn", 29 | "no-async-promise-executor": "warn", 30 | "no-await-in-loop": "warn", 31 | "no-case-declarations": "warn", 32 | "no-class-assign": "warn", 33 | "no-compare-neg-zero": "warn", 34 | "no-cond-assign": "warn", 35 | "no-const-assign": "warn", 36 | "no-constant-condition": "warn", 37 | "no-control-regex": "warn", 38 | "no-debugger": "warn", 39 | "no-delete-var": "warn", 40 | "no-dupe-args": "warn", 41 | "no-dupe-class-members": "warn", 42 | "no-dupe-else-if": "warn", 43 | "no-dupe-keys": "warn", 44 | "no-duplicate-case": "warn", 45 | "no-empty": "warn", 46 | "no-empty-character-class": "warn", 47 | "@typescript-eslint/no-empty-interface": "warn", 48 | "no-empty-pattern": "warn", 49 | "no-eval": "warn", 50 | "no-ex-assign": "warn", 51 | "@typescript-eslint/no-explicit-any": "warn", 52 | "no-extra-boolean-cast": "warn", 53 | "@typescript-eslint/no-extra-non-null-assertion": "warn", 54 | "no-extra-semi": "warn", 55 | "no-func-assign": "warn", 56 | "@typescript-eslint/no-inferrable-types": "warn", 57 | "@typescript-eslint/no-misused-new": "warn", 58 | "@typescript-eslint/no-namespace": "warn", 59 | "no-new-symbol": "warn", 60 | "@typescript-eslint/no-non-null-asserted-optional-chain": "warn", 61 | "@typescript-eslint/no-non-null-assertion": "warn", 62 | "no-obj-calls": "warn", 63 | "no-octal": "warn", 64 | "no-prototype-builtins": "warn", 65 | "no-regex-spaces": "warn", 66 | "no-setter-return": "warn", 67 | "no-shadow-restricted-names": "warn", 68 | "no-sparse-arrays": "warn", 69 | "@typescript-eslint/no-this-alias": "warn", 70 | "no-this-before-super": "warn", 71 | "no-throw-literal": "warn", 72 | "no-unexpected-multiline": "warn", 73 | "no-unsafe-finally": "warn", 74 | "no-unsafe-negation": "warn", 75 | "no-unused-labels": "warn", 76 | "no-var": "warn", 77 | "no-with": "warn", 78 | "@typescript-eslint/prefer-as-const": "warn", 79 | "@typescript-eslint/prefer-namespace-keyword": "warn", 80 | "require-yield": "warn", 81 | "@typescript-eslint/triple-slash-reference": "warn", 82 | "use-isnan": "warn", 83 | "valid-typeof": "warn", 84 | }, 85 | }; 86 | -------------------------------------------------------------------------------- /benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /benchmarks/benchmarks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | bench, 3 | BenchmarkTimer, 4 | runBenchmarks, 5 | } from "https://deno.land/std@0.67.0/testing/bench.ts"; 6 | import { expandGlobSync } from "https://deno.land/std@0.67.0/fs/expand_glob.ts"; 7 | 8 | const RUN_COUNT = 5; 9 | 10 | const files = [ 11 | ...expandGlobSync("**/*.ts", { 12 | root: "./benchmarks/oak", 13 | }), 14 | ].map((e) => e.path); 15 | 16 | bench({ 17 | name: "deno_lint", 18 | runs: RUN_COUNT, 19 | async func(b: BenchmarkTimer): Promise { 20 | b.start(); 21 | const proc = new Deno.Command("./target/release/examples/dlint", { 22 | args: ["run", ...files], 23 | stdout: "null", 24 | stderr: "null", 25 | }).spawn(); 26 | 27 | // No assert on success, cause dlint returns exit 28 | // code 1 if there's any problem. 29 | await proc.status; 30 | b.stop(); 31 | }, 32 | }); 33 | 34 | bench({ 35 | name: "eslint", 36 | runs: RUN_COUNT, 37 | async func(b: BenchmarkTimer): Promise { 38 | b.start(); 39 | const proc = new Deno.Command("npm", { 40 | args: ["run", "eslint", ...files], 41 | cwd: Deno.build.os === "windows" ? ".\\benchmarks" : "./benchmarks", 42 | stdout: "null", 43 | stderr: "null", 44 | }).spawn(); 45 | const { success } = await proc.status; 46 | if (!success) { 47 | // await Deno.copy(proc.stdout!, Deno.stdout); 48 | // await Deno.copy(proc.stderr!, Deno.stderr); 49 | throw Error("Failed to run eslint"); 50 | } 51 | b.stop(); 52 | }, 53 | }); 54 | 55 | const data = await runBenchmarks({ silent: true }); 56 | 57 | console.log(JSON.stringify(data.results)); 58 | -------------------------------------------------------------------------------- /benchmarks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dlint-benchmarks", 3 | "devDependencies": { 4 | "@typescript-eslint/eslint-plugin": "^3.1.0", 5 | "@typescript-eslint/parser": "^3.1.0", 6 | "eslint": "^7.1.0" 7 | }, 8 | "dependencies": { 9 | "typescript": "^3.9.3" 10 | }, 11 | "scripts": { 12 | "eslint": "eslint" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "lock": false, 3 | "exclude": [ 4 | "target", 5 | "examples", 6 | "benchmarks/oak" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /examples/dlint/diagnostics.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use deno_ast::diagnostics::Diagnostic; 4 | use deno_lint::diagnostic::LintDiagnostic; 5 | 6 | pub fn display_diagnostics( 7 | diagnostics: &[LintDiagnostic], 8 | format: Option<&str>, 9 | ) { 10 | match format { 11 | Some("compact") => print_compact(diagnostics), 12 | Some("pretty") => print_pretty(diagnostics), 13 | _ => unreachable!("Invalid output format specified"), 14 | } 15 | } 16 | 17 | fn print_compact(diagnostics: &[LintDiagnostic]) { 18 | for diagnostic in diagnostics { 19 | match &diagnostic.range { 20 | Some(range) => { 21 | let display_index = 22 | range.text_info.line_and_column_display(range.range.start); 23 | eprintln!( 24 | "{}: line {}, col {}, Error - {} ({})", 25 | diagnostic.specifier, 26 | display_index.line_number, 27 | display_index.column_number, 28 | diagnostic.details.message, 29 | diagnostic.details.code 30 | ) 31 | } 32 | None => { 33 | eprintln!( 34 | "{}: {} ({})", 35 | diagnostic.specifier, 36 | diagnostic.message(), 37 | diagnostic.code() 38 | ) 39 | } 40 | } 41 | } 42 | } 43 | 44 | fn print_pretty(diagnostics: &[LintDiagnostic]) { 45 | for diagnostic in diagnostics { 46 | eprintln!("{}\n", diagnostic.display()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/dlint/example_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "tags": ["recommended"], 4 | "include": [ 5 | "ban-untagged-todo" 6 | ], 7 | "exclude": [ 8 | "no-explicit-any" 9 | ] 10 | }, 11 | "files": { 12 | "include": [ 13 | "benchmarks/oak/**/*.ts" 14 | ], 15 | "exclude": [] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/dlint/rules.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use deno_lint::rules::get_all_rules; 4 | use deno_lint::tags; 5 | use serde::Serialize; 6 | 7 | #[derive(Clone, Serialize)] 8 | pub struct Rule { 9 | code: &'static str, 10 | docs: String, 11 | tags: Vec<&'static str>, 12 | } 13 | 14 | pub fn get_all_rules_metadata() -> Vec { 15 | get_all_rules() 16 | .iter() 17 | .map(|rule| Rule { 18 | code: rule.code(), 19 | docs: format!("https://docs.deno.com/lint/rules/{}", rule.code()), 20 | tags: rule.tags().iter().map(|tag| tag.display()).collect(), 21 | }) 22 | .collect() 23 | } 24 | 25 | pub fn get_specific_rule_metadata(rule_name: &str) -> Vec { 26 | get_all_rules_metadata() 27 | .into_iter() 28 | .filter(|r| r.code == rule_name) 29 | .collect() 30 | } 31 | 32 | pub fn print_rules(mut rules: Vec) { 33 | #[cfg(windows)] 34 | ansi_term::enable_ansi_support().expect("Failed to enable ANSI support"); 35 | 36 | match F::format(&mut rules) { 37 | Err(e) => { 38 | eprintln!("{}", e); 39 | std::process::exit(1); 40 | } 41 | Ok(text) => { 42 | println!("{}", text); 43 | } 44 | } 45 | } 46 | pub enum JsonFormatter {} 47 | pub enum PrettyFormatter {} 48 | 49 | pub trait RuleFormatter { 50 | fn format(rules: &mut [Rule]) -> Result; 51 | } 52 | 53 | impl RuleFormatter for JsonFormatter { 54 | fn format(rules: &mut [Rule]) -> Result { 55 | if rules.is_empty() { 56 | return Err("Rule not found!"); 57 | } 58 | serde_json::to_string_pretty(rules).map_err(|_| "failed to format!") 59 | } 60 | } 61 | 62 | impl RuleFormatter for PrettyFormatter { 63 | fn format(rules: &mut [Rule]) -> Result { 64 | match rules { 65 | // Unknown rule name is specified. 66 | [] => Err("Rule not found!"), 67 | 68 | // Certain rule name is specified. 69 | // Print its documentation richly. 70 | [rule] => Ok(format!("Documentation: {}", rule.docs)), 71 | 72 | // No rule name is specified. 73 | // Print the list of all rules. 74 | rules => { 75 | rules.sort_by_key(|r| r.code); 76 | let mut list = Vec::with_capacity(1 + rules.len()); 77 | list.push("Available rules (trailing ✔️ mark indicates it is included in the recommended rule set):".to_string()); 78 | list.extend(rules.iter().map(|r| { 79 | let mut s = format!(" - {}", r.code); 80 | if r.tags.contains(&tags::RECOMMENDED.display()) { 81 | s += " ✔️"; 82 | } 83 | s 84 | })); 85 | Ok(list.join("\n")) 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /examples/dlint/testdata/issue1145_no_trailing_newline.out: -------------------------------------------------------------------------------- 1 | error[no-var]: `var` keyword is not allowed. 2 | --> [WILDCARD]issue1145_no_trailing_newline.ts:3:1 3 | | 4 | 3 | var base 5 | | ^^^ 6 | 7 | docs: https://docs.deno.com/lint/rules/no-var 8 | 9 | 10 | error[no-unused-vars]: `base` is never used 11 | --> [WILDCARD]issue1145_no_trailing_newline.ts:3:5 12 | | 13 | 3 | var base 14 | | ^^^^ 15 | = hint: If this is intentional, prefix it with an underscore like `_base` 16 | 17 | docs: https://docs.deno.com/lint/rules/no-unused-vars 18 | 19 | 20 | Found 2 problems 21 | -------------------------------------------------------------------------------- /examples/dlint/testdata/issue1145_no_trailing_newline.ts: -------------------------------------------------------------------------------- 1 | /*--- 2 | ---*/ 3 | var base -------------------------------------------------------------------------------- /examples/dlint/testdata/simple.out: -------------------------------------------------------------------------------- 1 | error[no-unused-vars]: `hello` is never used 2 | --> [WILDCARD]simple.ts:1:10 3 | | 4 | 1 | function hello(): any { 5 | | ^^^^^ 6 | = hint: If this is intentional, prefix it with an underscore like `_hello` 7 | 8 | docs: https://docs.deno.com/lint/rules/no-unused-vars 9 | 10 | 11 | error[no-explicit-any]: `any` type is not allowed 12 | --> [WILDCARD]simple.ts:1:19 13 | | 14 | 1 | function hello(): any { 15 | | ^^^ 16 | = hint: Use a specific type other than `any` 17 | 18 | docs: https://docs.deno.com/lint/rules/no-explicit-any 19 | 20 | 21 | Found 2 problems 22 | -------------------------------------------------------------------------------- /examples/dlint/testdata/simple.ts: -------------------------------------------------------------------------------- 1 | function hello(): any { 2 | 3 | } -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.85.0" 3 | components = ["clippy", "rustfmt"] 4 | -------------------------------------------------------------------------------- /schemas/rules.v1.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "enum": [ 4 | "adjacent-overload-signatures", 5 | "ban-ts-comment", 6 | "ban-types", 7 | "ban-unknown-rule-code", 8 | "ban-untagged-ignore", 9 | "ban-untagged-todo", 10 | "ban-unused-ignore", 11 | "button-has-type", 12 | "camelcase", 13 | "constructor-super", 14 | "default-param-last", 15 | "eqeqeq", 16 | "explicit-function-return-type", 17 | "explicit-module-boundary-types", 18 | "for-direction", 19 | "fresh-handler-export", 20 | "fresh-server-event-handlers", 21 | "getter-return", 22 | "guard-for-in", 23 | "jsx-boolean-value", 24 | "jsx-curly-braces", 25 | "jsx-key", 26 | "jsx-no-children-prop", 27 | "jsx-no-comment-text-nodes", 28 | "jsx-no-danger-with-children", 29 | "jsx-no-duplicate-props", 30 | "jsx-no-unescaped-entities", 31 | "jsx-no-useless-fragment", 32 | "jsx-props-no-spread-multi", 33 | "jsx-void-dom-elements-no-children", 34 | "no-array-constructor", 35 | "no-async-promise-executor", 36 | "no-await-in-loop", 37 | "no-await-in-sync-fn", 38 | "no-boolean-literal-for-arguments", 39 | "no-case-declarations", 40 | "no-class-assign", 41 | "no-compare-neg-zero", 42 | "no-cond-assign", 43 | "no-console", 44 | "no-const-assign", 45 | "no-constant-condition", 46 | "no-control-regex", 47 | "no-danger", 48 | "no-debugger", 49 | "no-delete-var", 50 | "no-deprecated-deno-api", 51 | "no-dupe-args", 52 | "no-dupe-class-members", 53 | "no-dupe-else-if", 54 | "no-dupe-keys", 55 | "no-duplicate-case", 56 | "no-empty", 57 | "no-empty-character-class", 58 | "no-empty-enum", 59 | "no-empty-interface", 60 | "no-empty-pattern", 61 | "no-eval", 62 | "no-ex-assign", 63 | "no-explicit-any", 64 | "no-external-import", 65 | "no-extra-boolean-cast", 66 | "no-extra-non-null-assertion", 67 | "no-fallthrough", 68 | "no-func-assign", 69 | "no-global-assign", 70 | "no-implicit-declare-namespace-export", 71 | "no-import-assertions", 72 | "no-import-assign", 73 | "no-inferrable-types", 74 | "no-inner-declarations", 75 | "no-invalid-regexp", 76 | "no-invalid-triple-slash-reference", 77 | "no-irregular-whitespace", 78 | "no-misused-new", 79 | "no-namespace", 80 | "no-new-symbol", 81 | "no-node-globals", 82 | "no-non-null-asserted-optional-chain", 83 | "no-non-null-assertion", 84 | "no-obj-calls", 85 | "no-octal", 86 | "no-process-global", 87 | "no-prototype-builtins", 88 | "no-redeclare", 89 | "no-regex-spaces", 90 | "no-self-assign", 91 | "no-self-compare", 92 | "no-setter-return", 93 | "no-shadow-restricted-names", 94 | "no-sloppy-imports", 95 | "no-slow-types", 96 | "no-sparse-arrays", 97 | "no-sync-fn-in-async-fn", 98 | "no-this-alias", 99 | "no-this-before-super", 100 | "no-throw-literal", 101 | "no-top-level-await", 102 | "no-undef", 103 | "no-unreachable", 104 | "no-unsafe-finally", 105 | "no-unsafe-negation", 106 | "no-unused-labels", 107 | "no-unused-vars", 108 | "no-useless-rename", 109 | "no-var", 110 | "no-window", 111 | "no-window-prefix", 112 | "no-with", 113 | "prefer-as-const", 114 | "prefer-ascii", 115 | "prefer-const", 116 | "prefer-namespace-keyword", 117 | "prefer-primordials", 118 | "require-await", 119 | "require-yield", 120 | "rules-of-hooks", 121 | "single-var-declarator", 122 | "triple-slash-reference", 123 | "use-isnan", 124 | "valid-typeof", 125 | "verbatim-module-syntax" 126 | ] 127 | } 128 | -------------------------------------------------------------------------------- /schemas/tags.v1.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "enum": ["fresh", "jsr", "jsx", "react", "recommended"] 4 | } 5 | -------------------------------------------------------------------------------- /src/ast_parser.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use deno_ast::get_syntax; 4 | use deno_ast::MediaType; 5 | use deno_ast::ModuleSpecifier; 6 | use deno_ast::ParseDiagnostic; 7 | use deno_ast::ParsedSource; 8 | 9 | pub(crate) fn parse_program( 10 | specifier: ModuleSpecifier, 11 | media_type: MediaType, 12 | source_code: String, 13 | ) -> Result { 14 | let syntax = get_syntax(media_type); 15 | deno_ast::parse_program(deno_ast::ParseParams { 16 | specifier, 17 | media_type, 18 | text: source_code.into(), 19 | capture_tokens: true, 20 | maybe_syntax: Some(syntax), 21 | scope_analysis: true, 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /src/diagnostic.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use std::borrow::Cow; 4 | 5 | use deno_ast::diagnostics::Diagnostic; 6 | use deno_ast::diagnostics::DiagnosticLevel; 7 | use deno_ast::diagnostics::DiagnosticLocation; 8 | use deno_ast::diagnostics::DiagnosticSnippet; 9 | use deno_ast::diagnostics::DiagnosticSnippetHighlight; 10 | use deno_ast::diagnostics::DiagnosticSnippetHighlightStyle; 11 | use deno_ast::diagnostics::DiagnosticSourcePos; 12 | use deno_ast::diagnostics::DiagnosticSourceRange; 13 | use deno_ast::ModuleSpecifier; 14 | use deno_ast::SourceRange; 15 | use deno_ast::SourceTextInfo; 16 | 17 | #[derive(Debug, Clone)] 18 | pub struct LintFixChange { 19 | pub new_text: Cow<'static, str>, 20 | pub range: SourceRange, 21 | } 22 | 23 | #[derive(Debug, Clone)] 24 | pub struct LintFix { 25 | pub description: Cow<'static, str>, 26 | pub changes: Vec, 27 | } 28 | 29 | #[derive(Clone)] 30 | pub struct LintDiagnosticRange { 31 | pub text_info: SourceTextInfo, 32 | pub range: SourceRange, 33 | /// Additional information displayed beside the highlighted range. 34 | pub description: Option, 35 | } 36 | 37 | #[derive(Clone, Default)] 38 | pub enum LintDocsUrl { 39 | #[default] 40 | Default, 41 | None, 42 | Custom(String), 43 | } 44 | 45 | #[derive(Clone)] 46 | pub struct LintDiagnosticDetails { 47 | pub message: String, 48 | pub code: String, 49 | pub hint: Option, 50 | /// Fixes that should be shown in the Deno LSP and also 51 | /// used for the `deno lint --fix` flag. 52 | /// 53 | /// Note: If there are multiple fixes for a diagnostic then 54 | /// only the first fix will be used for the `--fix` flag, but 55 | /// multiple will be shown in the LSP. 56 | pub fixes: Vec, 57 | /// URL to the lint rule documentation. By default, the url uses the 58 | /// code to link to lint.deno.land 59 | pub custom_docs_url: LintDocsUrl, 60 | /// Displays additional information at the end of a diagnostic. 61 | pub info: Vec>, 62 | } 63 | 64 | #[derive(Clone)] 65 | pub struct LintDiagnostic { 66 | pub specifier: ModuleSpecifier, 67 | /// Optional range within the file. 68 | /// 69 | /// Diagnostics that don't have a range mean there's something wrong with 70 | /// the whole file. 71 | pub range: Option, 72 | pub details: LintDiagnosticDetails, 73 | } 74 | 75 | impl Diagnostic for LintDiagnostic { 76 | fn level(&self) -> DiagnosticLevel { 77 | DiagnosticLevel::Error 78 | } 79 | 80 | fn code(&self) -> Cow<'_, str> { 81 | Cow::Borrowed(&self.details.code) 82 | } 83 | 84 | fn message(&self) -> Cow<'_, str> { 85 | Cow::Borrowed(&self.details.message) 86 | } 87 | 88 | fn location(&self) -> DiagnosticLocation { 89 | match &self.range { 90 | Some(range) => DiagnosticLocation::ModulePosition { 91 | specifier: Cow::Borrowed(&self.specifier), 92 | text_info: Cow::Borrowed(&range.text_info), 93 | source_pos: DiagnosticSourcePos::SourcePos(range.range.start), 94 | }, 95 | None => DiagnosticLocation::Module { 96 | specifier: Cow::Borrowed(&self.specifier), 97 | }, 98 | } 99 | } 100 | 101 | fn snippet(&self) -> Option> { 102 | let range = self.range.as_ref()?; 103 | Some(DiagnosticSnippet { 104 | source: Cow::Borrowed(&range.text_info), 105 | highlights: vec![DiagnosticSnippetHighlight { 106 | range: DiagnosticSourceRange { 107 | start: DiagnosticSourcePos::SourcePos(range.range.start), 108 | end: DiagnosticSourcePos::SourcePos(range.range.end), 109 | }, 110 | style: DiagnosticSnippetHighlightStyle::Error, 111 | description: range.description.as_deref().map(Cow::Borrowed), 112 | }], 113 | }) 114 | } 115 | 116 | fn hint(&self) -> Option> { 117 | self 118 | .details 119 | .hint 120 | .as_ref() 121 | .map(|s| Cow::Borrowed(s.as_str())) 122 | } 123 | 124 | fn snippet_fixed(&self) -> Option> { 125 | None // todo 126 | } 127 | 128 | fn info(&self) -> Cow<'_, [std::borrow::Cow<'_, str>]> { 129 | Cow::Borrowed(&self.details.info) 130 | } 131 | 132 | fn docs_url(&self) -> Option> { 133 | match &self.details.custom_docs_url { 134 | LintDocsUrl::Default => Some(Cow::Owned(format!( 135 | "https://docs.deno.com/lint/rules/{}", 136 | &self.details.code 137 | ))), 138 | LintDocsUrl::Custom(url) => Some(Cow::Borrowed(url)), 139 | LintDocsUrl::None => None, 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/performance_mark.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use std::time::Instant; 4 | 5 | /// A struct to measure how long a function takes to execute. 6 | /// 7 | /// When the struct is dropped, `debug!` is used to print the measurement. 8 | pub struct PerformanceMark { 9 | name: &'static str, 10 | start: Option, 11 | } 12 | 13 | impl PerformanceMark { 14 | pub fn new(name: &'static str) -> Self { 15 | Self { 16 | name, 17 | start: if log::log_enabled!(log::Level::Debug) { 18 | Some(Instant::now()) 19 | } else { 20 | None 21 | }, 22 | } 23 | } 24 | } 25 | 26 | impl Drop for PerformanceMark { 27 | fn drop(&mut self) { 28 | if let Some(start) = self.start { 29 | let end = Instant::now(); 30 | debug!("{} took {:#?}", self.name, end - start); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/rules/ban_unknown_rule_code.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::{ 5 | tags::{self, Tags}, 6 | Program, 7 | }; 8 | 9 | /// This is a dummy struct just for having the docs. 10 | /// The actual implementation resides in [`Context`]. 11 | #[derive(Debug)] 12 | pub struct BanUnknownRuleCode; 13 | 14 | pub(crate) const CODE: &str = "ban-unknown-rule-code"; 15 | 16 | impl LintRule for BanUnknownRuleCode { 17 | fn tags(&self) -> Tags { 18 | &[tags::RECOMMENDED] 19 | } 20 | 21 | fn code(&self) -> &'static str { 22 | CODE 23 | } 24 | 25 | fn lint_program_with_ast_view( 26 | &self, 27 | _context: &mut Context, 28 | _program: Program<'_>, 29 | ) { 30 | // noop 31 | } 32 | 33 | // This rule should be run second to last. 34 | fn priority(&self) -> u32 { 35 | u32::MAX - 1 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/rules/ban_untagged_ignore.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::{ 5 | tags::{self, Tags}, 6 | Program, 7 | }; 8 | use deno_ast::SourceRange; 9 | 10 | #[derive(Debug)] 11 | pub struct BanUntaggedIgnore; 12 | 13 | const CODE: &str = "ban-untagged-ignore"; 14 | 15 | impl LintRule for BanUntaggedIgnore { 16 | fn tags(&self) -> Tags { 17 | &[tags::RECOMMENDED] 18 | } 19 | 20 | fn code(&self) -> &'static str { 21 | CODE 22 | } 23 | 24 | fn lint_program_with_ast_view( 25 | &self, 26 | context: &mut Context, 27 | _program: Program, 28 | ) { 29 | let mut violated_ranges: Vec = context 30 | .file_ignore_directive() 31 | .iter() 32 | .filter(|d| d.ignore_all()) 33 | .map(|d| d.range()) 34 | .collect(); 35 | 36 | violated_ranges.extend( 37 | context 38 | .line_ignore_directives() 39 | .values() 40 | .filter(|d| d.ignore_all()) 41 | .map(|d| d.range()), 42 | ); 43 | 44 | for range in violated_ranges { 45 | context.add_diagnostic_with_hint( 46 | range, 47 | CODE, 48 | "Ignore directive requires lint rule name(s)", 49 | "Add one or more lint rule names. E.g. // deno-lint-ignore adjacent-overload-signatures", 50 | ) 51 | } 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | 59 | #[test] 60 | fn ban_untagged_ignore_valid() { 61 | assert_lint_ok! { 62 | BanUntaggedIgnore, 63 | r#" 64 | // deno-lint-ignore no-explicit-any 65 | export const foo: any = 42; 66 | "#, 67 | }; 68 | } 69 | 70 | #[test] 71 | fn ban_untagged_ignore_invalid() { 72 | assert_lint_err! { 73 | BanUntaggedIgnore, 74 | r#" 75 | // deno-lint-ignore 76 | export const foo: any = 42; 77 | "#: [ 78 | { 79 | line: 2, 80 | col: 0, 81 | message: "Ignore directive requires lint rule name(s)", 82 | hint: "Add one or more lint rule names. E.g. // deno-lint-ignore adjacent-overload-signatures", 83 | } 84 | ] 85 | }; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/rules/ban_untagged_todo.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::Program; 5 | use deno_ast::swc::common::comments::Comment; 6 | use deno_ast::swc::common::comments::CommentKind; 7 | use deno_ast::SourceRangedForSpanned; 8 | use once_cell::sync::Lazy; 9 | use regex::Regex; 10 | 11 | #[derive(Debug)] 12 | pub struct BanUntaggedTodo; 13 | 14 | const CODE: &str = "ban-untagged-todo"; 15 | const MESSAGE: &str = "TODO should be tagged with (@username) or (#issue)"; 16 | const HINT: &str = "Add a user tag or issue reference to the TODO comment, e.g. TODO(@djones), TODO(djones), TODO(#123)"; 17 | 18 | impl LintRule for BanUntaggedTodo { 19 | fn code(&self) -> &'static str { 20 | CODE 21 | } 22 | 23 | fn lint_program_with_ast_view( 24 | &self, 25 | context: &mut Context, 26 | _program: Program, 27 | ) { 28 | let mut violated_comment_ranges = Vec::new(); 29 | 30 | violated_comment_ranges.extend(context.all_comments().filter_map(|c| { 31 | if check_comment(c) { 32 | Some(c.range()) 33 | } else { 34 | None 35 | } 36 | })); 37 | 38 | for range in violated_comment_ranges { 39 | context.add_diagnostic_with_hint(range, CODE, MESSAGE, HINT); 40 | } 41 | } 42 | } 43 | 44 | /// Returns `true` if the comment should be reported. 45 | fn check_comment(comment: &Comment) -> bool { 46 | if comment.kind != CommentKind::Line { 47 | return false; 48 | } 49 | 50 | let text = comment.text.to_lowercase(); 51 | let text = text.trim_start(); 52 | 53 | if !text.starts_with("todo") { 54 | return false; 55 | } 56 | 57 | static TODO_RE: Lazy = 58 | Lazy::new(|| Regex::new(r"todo\((#|@)?\S+\)").unwrap()); 59 | 60 | if TODO_RE.is_match(text) { 61 | return false; 62 | } 63 | 64 | true 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use super::*; 70 | 71 | #[test] 72 | fn ban_ts_ignore_valid() { 73 | assert_lint_ok! { 74 | BanUntaggedTodo, 75 | r#" 76 | // TODO(@someusername) 77 | const c = "c"; 78 | "#, 79 | r#" 80 | // TODO(@someusername) this should be fixed in next release 81 | const c = "c"; 82 | "#, 83 | r#" 84 | // TODO(someusername) 85 | const c = "c"; 86 | "#, 87 | r#" 88 | // TODO(someusername) this should be fixed in next release 89 | const c = "c"; 90 | "#, 91 | r#" 92 | // TODO(#1234) 93 | const b = "b"; 94 | "#, 95 | r#" 96 | // TODO(#1234) this should be fixed in next release 97 | const b = "b"; 98 | "#, 99 | }; 100 | } 101 | 102 | #[test] 103 | fn ban_ts_ignore_invalid() { 104 | assert_lint_err! { 105 | BanUntaggedTodo, 106 | r#" 107 | // TODO 108 | function foo() { 109 | // pass 110 | } 111 | "#: [{ col: 0, line: 2, message: MESSAGE, hint: HINT }], 112 | r#" 113 | // TODO this should be fixed in next release (username) 114 | const a = "a"; 115 | "#: [{ col: 0, line: 2, message: MESSAGE, hint: HINT }], 116 | r#" 117 | // TODO this should be fixed in next release (#1234) 118 | const b = "b"; 119 | "#: [{ col: 0, line: 2, message: MESSAGE, hint: HINT }], 120 | r#" 121 | // TODO this should be fixed in next release (@someusername) 122 | const c = "c"; 123 | "#: [{ col: 0, line: 2, message: MESSAGE, hint: HINT }], 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/rules/ban_unused_ignore.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::{ 5 | tags::{self, Tags}, 6 | Program, 7 | }; 8 | 9 | /// This is a dummy struct just for having the docs. 10 | /// The actual implementation resides in [`Context`]. 11 | #[derive(Debug)] 12 | pub struct BanUnusedIgnore; 13 | 14 | impl LintRule for BanUnusedIgnore { 15 | fn tags(&self) -> Tags { 16 | &[tags::RECOMMENDED] 17 | } 18 | 19 | fn code(&self) -> &'static str { 20 | "ban-unused-ignore" 21 | } 22 | 23 | fn lint_program_with_ast_view( 24 | &self, 25 | _context: &mut Context, 26 | _program: Program<'_>, 27 | ) { 28 | // noop 29 | } 30 | 31 | // This rule should be run last. 32 | fn priority(&self) -> u32 { 33 | u32::MAX 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/rules/explicit_function_return_type.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::Program; 6 | use deno_ast::{view as ast_view, MediaType, SourceRanged}; 7 | use derive_more::Display; 8 | 9 | #[derive(Debug)] 10 | pub struct ExplicitFunctionReturnType; 11 | 12 | const CODE: &str = "explicit-function-return-type"; 13 | 14 | #[derive(Display)] 15 | enum ExplicitFunctionReturnTypeMessage { 16 | #[display(fmt = "Missing return type on function")] 17 | MissingRetType, 18 | } 19 | 20 | #[derive(Display)] 21 | enum ExplicitFunctionReturnTypeHint { 22 | #[display(fmt = "Add a return type to the function signature")] 23 | AddRetType, 24 | } 25 | 26 | impl LintRule for ExplicitFunctionReturnType { 27 | fn code(&self) -> &'static str { 28 | CODE 29 | } 30 | 31 | fn lint_program_with_ast_view( 32 | &self, 33 | context: &mut Context, 34 | program: Program, 35 | ) { 36 | // ignore js(x) files 37 | if matches!(context.media_type(), MediaType::JavaScript | MediaType::Jsx) { 38 | return; 39 | } 40 | ExplicitFunctionReturnTypeHandler.traverse(program, context); 41 | } 42 | } 43 | 44 | struct ExplicitFunctionReturnTypeHandler; 45 | 46 | impl Handler for ExplicitFunctionReturnTypeHandler { 47 | fn function(&mut self, function: &ast_view::Function, context: &mut Context) { 48 | let is_method_setter = matches!( 49 | function 50 | .parent() 51 | .to::() 52 | .map(|m| m.method_kind()), 53 | Some(ast_view::MethodKind::Setter) 54 | ); 55 | 56 | if function.return_type.is_none() && !is_method_setter { 57 | context.add_diagnostic_with_hint( 58 | function.range(), 59 | CODE, 60 | ExplicitFunctionReturnTypeMessage::MissingRetType, 61 | ExplicitFunctionReturnTypeHint::AddRetType, 62 | ); 63 | } 64 | } 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use super::*; 70 | 71 | #[test] 72 | fn explicit_function_return_type_valid() { 73 | assert_lint_ok! { 74 | ExplicitFunctionReturnType, 75 | "function fooTyped(): void { }", 76 | "const bar = (a: string) => { }", 77 | "const barTyped = (a: string): Promise => { }", 78 | "class Test { set test(value: string) {} }", 79 | "const obj = { set test(value: string) {} };", 80 | }; 81 | 82 | assert_lint_ok! { 83 | ExplicitFunctionReturnType, 84 | filename: "file:///foo.js", 85 | "function foo() { }", 86 | "const bar = (a) => { }", 87 | "class Test { set test(value) {} }", 88 | "const obj = { set test(value) {} };", 89 | }; 90 | 91 | assert_lint_ok! { 92 | ExplicitFunctionReturnType, 93 | filename: "file:///foo.jsx", 94 | "export function Foo(props) {return
{props.name}
}", 95 | "export default class Foo { render() { return
}}" 96 | }; 97 | } 98 | 99 | #[test] 100 | fn explicit_function_return_type_invalid() { 101 | assert_lint_err! { 102 | ExplicitFunctionReturnType, 103 | 104 | r#"function foo() { }"#: [ 105 | { 106 | col: 0, 107 | message: ExplicitFunctionReturnTypeMessage::MissingRetType, 108 | hint: ExplicitFunctionReturnTypeHint::AddRetType, 109 | }], 110 | r#" 111 | function a() { 112 | function b() {} 113 | } 114 | "#: [ 115 | { 116 | line: 2, 117 | col: 0, 118 | message: ExplicitFunctionReturnTypeMessage::MissingRetType, 119 | hint: ExplicitFunctionReturnTypeHint::AddRetType, 120 | }, 121 | { 122 | line: 3, 123 | col: 2, 124 | message: ExplicitFunctionReturnTypeMessage::MissingRetType, 125 | hint: ExplicitFunctionReturnTypeHint::AddRetType, 126 | }, 127 | ] 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/rules/fresh_handler_export.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | 7 | use deno_ast::view::{Decl, Pat, Program}; 8 | use deno_ast::SourceRanged; 9 | 10 | #[derive(Debug)] 11 | pub struct FreshHandlerExport; 12 | 13 | const CODE: &str = "fresh-handler-export"; 14 | const MESSAGE: &str = 15 | "Fresh middlewares must be exported as \"handler\" but got \"handlers\" instead."; 16 | const HINT: &str = "Did you mean \"handler\"?"; 17 | 18 | impl LintRule for FreshHandlerExport { 19 | fn tags(&self) -> Tags { 20 | &[tags::FRESH] 21 | } 22 | 23 | fn code(&self) -> &'static str { 24 | CODE 25 | } 26 | 27 | fn lint_program_with_ast_view( 28 | &self, 29 | context: &mut Context, 30 | program: Program, 31 | ) { 32 | Visitor.traverse(program, context); 33 | } 34 | } 35 | 36 | struct Visitor; 37 | 38 | impl Handler for Visitor { 39 | fn export_decl( 40 | &mut self, 41 | export_decl: &deno_ast::view::ExportDecl, 42 | ctx: &mut Context, 43 | ) { 44 | // Fresh only considers components in the routes/ folder to be 45 | // server components. 46 | let Some(mut path_segments) = ctx.specifier().path_segments() else { 47 | return; 48 | }; 49 | if !path_segments.any(|part| part == "routes") { 50 | return; 51 | } 52 | 53 | let id = match export_decl.decl { 54 | Decl::Var(var_decl) => { 55 | if let Some(first) = var_decl.decls.first() { 56 | let Pat::Ident(name_ident) = first.name else { 57 | return; 58 | }; 59 | name_ident.id 60 | } else { 61 | return; 62 | } 63 | } 64 | Decl::Fn(fn_decl) => fn_decl.ident, 65 | _ => return, 66 | }; 67 | 68 | // Fresh middleware handler must be exported as "handler" not "handlers" 69 | if id.sym().eq("handlers") { 70 | ctx.add_diagnostic_with_hint(id.range(), CODE, MESSAGE, HINT); 71 | } 72 | } 73 | } 74 | 75 | #[cfg(test)] 76 | mod tests { 77 | use super::*; 78 | 79 | #[test] 80 | fn fresh_handler_export_name() { 81 | assert_lint_ok!( 82 | FreshHandlerExport, 83 | filename: "file:///foo.jsx", 84 | "const handler = {}", 85 | ); 86 | assert_lint_ok!( 87 | FreshHandlerExport, 88 | filename: "file:///foo.jsx", 89 | "function handler() {}", 90 | ); 91 | assert_lint_ok!( 92 | FreshHandlerExport, 93 | filename: "file:///foo.jsx", 94 | "export const handler = {}", 95 | ); 96 | assert_lint_ok!( 97 | FreshHandlerExport, 98 | filename: "file:///foo.jsx", 99 | "export const handlers = {}", 100 | ); 101 | assert_lint_ok!( 102 | FreshHandlerExport, 103 | filename: "file:///foo.jsx", 104 | "export function handlers() {}", 105 | ); 106 | 107 | assert_lint_ok!( 108 | FreshHandlerExport, 109 | filename: "file:///routes/foo.jsx", 110 | "export const handler = {}", 111 | ); 112 | assert_lint_ok!( 113 | FreshHandlerExport, 114 | filename: "file:///routes/foo.jsx", 115 | "export function handler() {}", 116 | ); 117 | assert_lint_ok!( 118 | FreshHandlerExport, 119 | filename: "file:///routes/foo.jsx", 120 | "export async function handler() {}", 121 | ); 122 | 123 | assert_lint_err!(FreshHandlerExport, filename: "file:///routes/index.tsx", r#"export const handlers = {}"#: [ 124 | { 125 | col: 13, 126 | message: MESSAGE, 127 | hint: HINT, 128 | }]); 129 | assert_lint_err!(FreshHandlerExport, filename: "file:///routes/index.tsx", r#"export function handlers() {}"#: [ 130 | { 131 | col: 16, 132 | message: MESSAGE, 133 | hint: HINT, 134 | }]); 135 | assert_lint_err!(FreshHandlerExport, filename: "file:///routes/index.tsx", r#"export async function handlers() {}"#: [ 136 | { 137 | col: 22, 138 | message: MESSAGE, 139 | hint: HINT, 140 | }]); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/rules/guard_for_in.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::Program; 6 | use deno_ast::SourceRanged; 7 | 8 | #[derive(Debug)] 9 | pub struct GuardForIn; 10 | 11 | const CODE: &str = "guard-for-in"; 12 | const MESSAGE: &str = "Require `for-in` loops to include an `if` statement"; 13 | const HINT: &str = "The body of a `for-in` should be wrapped in an `if` statement to filter unwanted properties from the prototype."; 14 | 15 | impl LintRule for GuardForIn { 16 | fn code(&self) -> &'static str { 17 | CODE 18 | } 19 | 20 | fn lint_program_with_ast_view( 21 | &self, 22 | context: &mut Context, 23 | program: Program<'_>, 24 | ) { 25 | GuardForInHandler.traverse(program, context); 26 | } 27 | } 28 | 29 | struct GuardForInHandler; 30 | 31 | impl Handler for GuardForInHandler { 32 | fn for_in_stmt( 33 | &mut self, 34 | for_in_stmt: &deno_ast::view::ForInStmt, 35 | ctx: &mut Context, 36 | ) { 37 | use deno_ast::view::Stmt::{Block, Continue, Empty, If}; 38 | 39 | match for_in_stmt.body { 40 | Empty(_) | If(_) => (), 41 | Block(block_stmt) => { 42 | match block_stmt.stmts[..] { 43 | // empty block 44 | [] => (), 45 | 46 | // block statement with only an if statement 47 | [stmt] => { 48 | if !matches!(stmt, If(_)) { 49 | ctx.add_diagnostic_with_hint( 50 | for_in_stmt.range(), 51 | CODE, 52 | MESSAGE, 53 | HINT, 54 | ); 55 | } 56 | } 57 | 58 | // block statement that start with an if statement with only a continue statement 59 | [first, ..] => { 60 | let If(if_stmt) = first else { 61 | ctx.add_diagnostic_with_hint( 62 | for_in_stmt.range(), 63 | CODE, 64 | MESSAGE, 65 | HINT, 66 | ); 67 | return; 68 | }; 69 | 70 | match if_stmt.cons { 71 | Continue(_) => (), 72 | Block(inner_block_stmt) => { 73 | if !matches!(inner_block_stmt.stmts[..], [Continue(_)]) { 74 | ctx.add_diagnostic_with_hint( 75 | for_in_stmt.range(), 76 | CODE, 77 | MESSAGE, 78 | HINT, 79 | ); 80 | } 81 | } 82 | _ => { 83 | ctx.add_diagnostic_with_hint( 84 | for_in_stmt.range(), 85 | CODE, 86 | MESSAGE, 87 | HINT, 88 | ); 89 | } 90 | } 91 | } 92 | } 93 | } 94 | _ => { 95 | ctx.add_diagnostic_with_hint(for_in_stmt.range(), CODE, MESSAGE, HINT); 96 | } 97 | }; 98 | } 99 | } 100 | 101 | #[cfg(test)] 102 | mod tests { 103 | use super::*; 104 | 105 | #[test] 106 | fn guard_for_in_valid() { 107 | assert_lint_ok! { 108 | GuardForIn, 109 | r#"for (const key in obj);"#, 110 | r#" 111 | for (const key in obj) 112 | if (Object.hasOwn(obj, key)) {} 113 | "#, 114 | r#" 115 | for (const key in obj) { 116 | if (Object.hasOwn(obj, key)) {} 117 | } 118 | "#, 119 | r#" 120 | for (const key in obj) { 121 | if (!Object.hasOwn(obj, key)) continue; 122 | } 123 | "#, 124 | r#" 125 | for (const key in obj) { 126 | if (!Object.hasOwn(obj, key)) continue; 127 | foo(obj, key); 128 | } 129 | "#, 130 | r#" 131 | for (const key in obj) { 132 | if (!Object.hasOwn(obj, key)) { 133 | continue; 134 | } 135 | } 136 | "#, 137 | r#" 138 | for (const key in obj) { 139 | if (!Object.hasOwn(obj, key)) { 140 | continue; 141 | } 142 | foo(obj, key); 143 | } 144 | "#, 145 | }; 146 | } 147 | 148 | #[test] 149 | fn guard_for_in_invalid() { 150 | assert_lint_err! { 151 | GuardForIn, 152 | MESSAGE, 153 | HINT, 154 | r#" 155 | for (const key in obj) 156 | foo(obj, key); 157 | "#: [{ line: 2, col: 0 }], 158 | r#" 159 | for (const key in obj) { 160 | foo(obj, key); 161 | } 162 | "#: [{ line: 2, col: 0 }], 163 | r#" 164 | for (const key in obj) { 165 | foo(obj, key); 166 | bar(obj, key); 167 | } 168 | "#: [{ line: 2, col: 0 }], 169 | r#" 170 | for (const key in obj) { 171 | if (!Object.hasOwn(obj, key)) { 172 | foo(obj, key); 173 | continue; 174 | } 175 | bar(obj, key); 176 | } 177 | "#: [{ line: 2, col: 0 }], 178 | }; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/rules/jsx_boolean_value.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::diagnostic::{LintFix, LintFixChange}; 5 | use crate::handler::{Handler, Traverse}; 6 | use crate::tags::Tags; 7 | use crate::{tags, Program}; 8 | use deno_ast::swc::parser::token::Token; 9 | use deno_ast::view::{AssignOp, Expr, JSXAttr, JSXAttrValue, JSXExpr, Lit}; 10 | use deno_ast::{SourceRange, SourceRanged, SourceRangedForSpanned}; 11 | 12 | #[derive(Debug)] 13 | pub struct JSXBooleanValue; 14 | 15 | const CODE: &str = "jsx-boolean-value"; 16 | 17 | impl LintRule for JSXBooleanValue { 18 | fn tags(&self) -> Tags { 19 | &[tags::RECOMMENDED, tags::REACT, tags::JSX] 20 | } 21 | 22 | fn code(&self) -> &'static str { 23 | CODE 24 | } 25 | 26 | fn lint_program_with_ast_view( 27 | &self, 28 | context: &mut Context, 29 | program: Program, 30 | ) { 31 | JSXBooleanValueHandler.traverse(program, context); 32 | } 33 | } 34 | 35 | const MESSAGE: &str = 36 | "Passing 'true' to boolean attributes is the same as not passing it`"; 37 | const HINT: &str = "Remove the attribute value"; 38 | const FIX_DESC: &str = HINT; 39 | 40 | struct JSXBooleanValueHandler; 41 | 42 | impl Handler for JSXBooleanValueHandler { 43 | fn jsx_attr(&mut self, node: &JSXAttr, ctx: &mut Context) { 44 | if let Some(value) = node.value { 45 | if let JSXAttrValue::JSXExprContainer(expr) = value { 46 | if let JSXExpr::Expr(Expr::Lit(Lit::Bool(lit_bool))) = expr.expr { 47 | if lit_bool.value() 48 | && lit_bool.leading_comments_fast(ctx.program()).is_empty() 49 | && lit_bool.trailing_comments_fast(ctx.program()).is_empty() 50 | { 51 | let mut fixes = Vec::with_capacity(1); 52 | if let Some(token) = expr.previous_token_fast(ctx.program()) { 53 | if token.token == Token::AssignOp(AssignOp::Assign) { 54 | let start_pos = token 55 | .previous_token_fast(ctx.program()) 56 | .map(|t| t.end()) 57 | .unwrap_or(token.start()); 58 | fixes.push(LintFix { 59 | description: FIX_DESC.into(), 60 | changes: vec![LintFixChange { 61 | new_text: "".into(), 62 | range: SourceRange::new(start_pos, expr.end()), 63 | }], 64 | }); 65 | } 66 | } 67 | ctx.add_diagnostic_with_fixes( 68 | value.range(), 69 | CODE, 70 | MESSAGE, 71 | Some(HINT.into()), 72 | fixes, 73 | ); 74 | } 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | // most tests are taken from ESlint, commenting those 82 | // requiring code path support 83 | #[cfg(test)] 84 | mod tests { 85 | use super::*; 86 | 87 | #[test] 88 | fn jsx_no_comment_text_nodes_valid() { 89 | assert_lint_ok! { 90 | JSXBooleanValue, 91 | filename: "file:///foo.jsx", 92 | // non derived classes. 93 | "", 94 | "", 95 | "", 96 | "", 97 | }; 98 | } 99 | 100 | #[test] 101 | fn jsx_no_comment_text_nodes_invalid() { 102 | assert_lint_err! { 103 | JSXBooleanValue, 104 | filename: "file:///foo.jsx", 105 | "": [ 106 | { 107 | col: 9, 108 | message: MESSAGE, 109 | hint: HINT, 110 | fix: (FIX_DESC, ""), 111 | } 112 | ], 113 | }; 114 | 115 | assert_lint_err! { 116 | JSXBooleanValue, 117 | filename: "file:///foo.jsx", 118 | "": [ 119 | { 120 | col: 11, 121 | message: MESSAGE, 122 | hint: HINT, 123 | fix: (FIX_DESC, ""), 124 | } 125 | ], 126 | }; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/rules/jsx_no_children_prop.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::view::{JSXAttrName, JSXAttrOrSpread, JSXOpeningElement}; 8 | use deno_ast::SourceRanged; 9 | 10 | #[derive(Debug)] 11 | pub struct JSXNoChildrenProp; 12 | 13 | const CODE: &str = "jsx-no-children-prop"; 14 | 15 | impl LintRule for JSXNoChildrenProp { 16 | fn tags(&self) -> Tags { 17 | &[tags::RECOMMENDED, tags::REACT, tags::JSX, tags::FRESH] 18 | } 19 | 20 | fn code(&self) -> &'static str { 21 | CODE 22 | } 23 | 24 | fn lint_program_with_ast_view( 25 | &self, 26 | context: &mut Context, 27 | program: Program, 28 | ) { 29 | JSXNoChildrenPropHandler.traverse(program, context); 30 | } 31 | } 32 | 33 | const MESSAGE: &str = "Avoid passing children as a prop"; 34 | 35 | struct JSXNoChildrenPropHandler; 36 | 37 | impl Handler for JSXNoChildrenPropHandler { 38 | fn jsx_opening_element( 39 | &mut self, 40 | node: &JSXOpeningElement, 41 | ctx: &mut Context, 42 | ) { 43 | for attr in node.attrs { 44 | if let JSXAttrOrSpread::JSXAttr(attr) = attr { 45 | if let JSXAttrName::Ident(id) = attr.name { 46 | if id.sym() == "children" { 47 | ctx.add_diagnostic(attr.range(), CODE, MESSAGE); 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | // most tests are taken from ESlint, commenting those 56 | // requiring code path support 57 | #[cfg(test)] 58 | mod tests { 59 | use super::*; 60 | 61 | #[test] 62 | fn jsx_no_children_prop_valid() { 63 | assert_lint_ok! { 64 | JSXNoChildrenProp, 65 | filename: "file:///foo.jsx", 66 | r#"
foo
"#, 67 | r#"
"#, 68 | }; 69 | } 70 | 71 | #[test] 72 | fn jsx_no_children_prop_invalid() { 73 | assert_lint_err! { 74 | JSXNoChildrenProp, 75 | filename: "file:///foo.jsx", 76 | r#"
"#: [ 77 | { 78 | col: 5, 79 | message: MESSAGE, 80 | } 81 | ], 82 | r#""#: [ 83 | { 84 | col: 5, 85 | message: MESSAGE, 86 | } 87 | ], 88 | r#"
"#: [ 89 | { 90 | col: 5, 91 | message: MESSAGE, 92 | } 93 | ], 94 | }; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/rules/jsx_no_comment_text_nodes.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::Tags; 6 | use crate::{tags, Program}; 7 | use deno_ast::view::JSXText; 8 | use deno_ast::SourceRanged; 9 | 10 | #[derive(Debug)] 11 | pub struct JSXNoCommentTextNodes; 12 | 13 | const CODE: &str = "jsx-no-comment-text-nodes"; 14 | 15 | impl LintRule for JSXNoCommentTextNodes { 16 | fn tags(&self) -> Tags { 17 | &[tags::RECOMMENDED, tags::REACT, tags::JSX, tags::FRESH] 18 | } 19 | 20 | fn code(&self) -> &'static str { 21 | CODE 22 | } 23 | 24 | fn lint_program_with_ast_view( 25 | &self, 26 | context: &mut Context, 27 | program: Program, 28 | ) { 29 | JSXNoCommentTextNodesHandler.traverse(program, context); 30 | } 31 | } 32 | 33 | const MESSAGE: &str = 34 | "Comments inside children should be placed inside curly braces"; 35 | 36 | struct JSXNoCommentTextNodesHandler; 37 | 38 | impl Handler for JSXNoCommentTextNodesHandler { 39 | fn jsx_text(&mut self, node: &JSXText, ctx: &mut Context) { 40 | let value = &node.inner.value; 41 | if value.starts_with("//") || value.starts_with("/*") { 42 | ctx.add_diagnostic(node.range(), CODE, MESSAGE); 43 | } 44 | } 45 | } 46 | 47 | // most tests are taken from ESlint, commenting those 48 | // requiring code path support 49 | #[cfg(test)] 50 | mod tests { 51 | use super::*; 52 | 53 | #[test] 54 | fn jsx_no_comment_text_nodes_valid() { 55 | assert_lint_ok! { 56 | JSXNoCommentTextNodes, 57 | filename: "file:///foo.jsx", 58 | // non derived classes. 59 | r#"
{/* comment */}
"#, 60 | }; 61 | } 62 | 63 | #[test] 64 | fn jsx_no_comment_text_nodes_invalid() { 65 | assert_lint_err! { 66 | JSXNoCommentTextNodes, 67 | filename: "file:///foo.jsx", 68 | "
// comment
": [ 69 | { 70 | col: 5, 71 | message: MESSAGE, 72 | } 73 | ], 74 | r#"
/* comment */
"#: [ 75 | { 76 | col: 5, 77 | message: MESSAGE, 78 | } 79 | ], 80 | }; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/rules/jsx_no_duplicate_props.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use std::collections::HashSet; 4 | 5 | use super::{Context, LintRule}; 6 | use crate::handler::{Handler, Traverse}; 7 | use crate::tags::{self, Tags}; 8 | use crate::Program; 9 | use deno_ast::view::{JSXAttrName, JSXAttrOrSpread, JSXOpeningElement}; 10 | use deno_ast::SourceRanged; 11 | 12 | #[derive(Debug)] 13 | pub struct JSXNoDuplicateProps; 14 | 15 | const CODE: &str = "jsx-no-duplicate-props"; 16 | 17 | impl LintRule for JSXNoDuplicateProps { 18 | fn tags(&self) -> Tags { 19 | &[tags::RECOMMENDED, tags::REACT, tags::JSX] 20 | } 21 | 22 | fn code(&self) -> &'static str { 23 | CODE 24 | } 25 | 26 | fn lint_program_with_ast_view( 27 | &self, 28 | context: &mut Context, 29 | program: Program, 30 | ) { 31 | JSXNoDuplicatedPropsHandler.traverse(program, context); 32 | } 33 | } 34 | 35 | const MESSAGE: &str = "Duplicate JSX attribute found."; 36 | const HINT: &str = "Remove the duplicated attribute."; 37 | 38 | struct JSXNoDuplicatedPropsHandler; 39 | 40 | impl Handler for JSXNoDuplicatedPropsHandler { 41 | fn jsx_opening_element( 42 | &mut self, 43 | node: &JSXOpeningElement, 44 | ctx: &mut Context, 45 | ) { 46 | let mut seen: HashSet<&'_ str> = HashSet::new(); 47 | for attr in node.attrs { 48 | if let JSXAttrOrSpread::JSXAttr(attr_name) = attr { 49 | if let JSXAttrName::Ident(id) = attr_name.name { 50 | let name = id.sym().as_str(); 51 | if seen.contains(name) { 52 | ctx.add_diagnostic_with_hint(id.range(), CODE, MESSAGE, HINT); 53 | } 54 | 55 | seen.insert(name); 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | // most tests are taken from ESlint, commenting those 63 | // requiring code path support 64 | #[cfg(test)] 65 | mod tests { 66 | use super::*; 67 | 68 | #[test] 69 | fn jsx_no_duplicate_props_valid() { 70 | assert_lint_ok! { 71 | JSXNoDuplicateProps, 72 | filename: "file:///foo.jsx", 73 | "", 74 | "
", 75 | }; 76 | } 77 | 78 | #[test] 79 | fn jsx_no_duplicate_props_invalid() { 80 | assert_lint_err! { 81 | JSXNoDuplicateProps, 82 | filename: "file:///foo.jsx", 83 | "
": [ 84 | { 85 | col: 7, 86 | message: MESSAGE, 87 | hint: HINT, 88 | } 89 | ], 90 | "": [ 91 | { 92 | col: 7, 93 | message: MESSAGE, 94 | hint: HINT, 95 | } 96 | ], 97 | "": [ 98 | { 99 | col: 14, 100 | message: MESSAGE, 101 | hint: HINT, 102 | } 103 | ], 104 | "
": [ 105 | { 106 | col: 14, 107 | message: MESSAGE, 108 | hint: HINT, 109 | } 110 | ] 111 | }; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/rules/jsx_no_unescaped_entities.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::diagnostic::{LintFix, LintFixChange}; 5 | use crate::handler::{Handler, Traverse}; 6 | use crate::tags::{self, Tags}; 7 | use crate::Program; 8 | use deno_ast::view::{JSXElement, JSXElementChild}; 9 | use deno_ast::SourceRanged; 10 | 11 | #[derive(Debug)] 12 | pub struct JSXNoUnescapedEntities; 13 | 14 | const CODE: &str = "jsx-no-unescaped-entities"; 15 | 16 | impl LintRule for JSXNoUnescapedEntities { 17 | fn tags(&self) -> Tags { 18 | &[tags::RECOMMENDED, tags::REACT, tags::JSX, tags::FRESH] 19 | } 20 | 21 | fn code(&self) -> &'static str { 22 | CODE 23 | } 24 | 25 | fn lint_program_with_ast_view( 26 | &self, 27 | context: &mut Context, 28 | program: Program, 29 | ) { 30 | JSXNoUnescapedEntitiesHandler.traverse(program, context); 31 | } 32 | } 33 | 34 | const MESSAGE: &str = "Found one or more unescaped entities in JSX text"; 35 | const HINT: &str = "Escape the >} characters respectively"; 36 | 37 | struct JSXNoUnescapedEntitiesHandler; 38 | 39 | impl Handler for JSXNoUnescapedEntitiesHandler { 40 | fn jsx_element(&mut self, node: &JSXElement, ctx: &mut Context) { 41 | for child in node.children { 42 | if let JSXElementChild::JSXText(jsx_text) = child { 43 | let text = jsx_text.raw().as_str(); 44 | let new_text = text.replace('>', ">").replace('}', "}"); 45 | 46 | if text != new_text { 47 | ctx.add_diagnostic_with_fixes( 48 | jsx_text.range(), 49 | CODE, 50 | MESSAGE, 51 | Some(HINT.to_string()), 52 | vec![LintFix { 53 | description: "Escape entities in the text node".into(), 54 | changes: vec![LintFixChange { 55 | new_text: new_text.into(), 56 | range: child.range(), 57 | }], 58 | }], 59 | ); 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | 70 | #[test] 71 | fn jsx_no_unescaped_entities_valid() { 72 | assert_lint_ok! { 73 | JSXNoUnescapedEntities, 74 | filename: "file:///foo.jsx", 75 | r#"
>
"#, 76 | r#"
{">"}
"#, 77 | r#"
{"}"}
"#, 78 | }; 79 | } 80 | 81 | #[test] 82 | fn jsx_no_unescaped_entities_invalid() { 83 | assert_lint_err! { 84 | JSXNoUnescapedEntities, 85 | filename: "file:///foo.jsx", 86 | r#"
>}
"#: [ 87 | { 88 | col: 5, 89 | message: MESSAGE, 90 | hint: HINT, 91 | fix: ( 92 | "Escape entities in the text node", 93 | "
>}
" 94 | ) 95 | } 96 | ] 97 | }; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/rules/jsx_no_useless_fragment.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::view::{JSXElement, JSXElementChild, JSXFragment}; 8 | use deno_ast::SourceRanged; 9 | 10 | #[derive(Debug)] 11 | pub struct JSXNoUselessFragment; 12 | 13 | const CODE: &str = "jsx-no-useless-fragment"; 14 | 15 | impl LintRule for JSXNoUselessFragment { 16 | fn tags(&self) -> Tags { 17 | &[tags::RECOMMENDED, tags::REACT, tags::JSX, tags::FRESH] 18 | } 19 | 20 | fn code(&self) -> &'static str { 21 | CODE 22 | } 23 | 24 | fn lint_program_with_ast_view( 25 | &self, 26 | context: &mut Context, 27 | program: Program, 28 | ) { 29 | JSXNoUselessFragmentHandler.traverse(program, context); 30 | } 31 | } 32 | 33 | const MESSAGE: &str = "Unnecessary Fragment detected"; 34 | const HINT: &str = "Remove this Fragment"; 35 | 36 | struct JSXNoUselessFragmentHandler; 37 | 38 | impl Handler for JSXNoUselessFragmentHandler { 39 | // Check root fragments 40 | fn jsx_fragment(&mut self, node: &JSXFragment, ctx: &mut Context) { 41 | if node.children.is_empty() { 42 | ctx.add_diagnostic_with_hint(node.range(), CODE, MESSAGE, HINT); 43 | } else if node.children.len() == 1 { 44 | if let Some( 45 | JSXElementChild::JSXElement(_) | JSXElementChild::JSXFragment(_), 46 | ) = &node.children.first() 47 | { 48 | ctx.add_diagnostic_with_hint(node.range(), CODE, MESSAGE, HINT); 49 | } 50 | } 51 | } 52 | 53 | fn jsx_element(&mut self, node: &JSXElement, ctx: &mut Context) { 54 | for child in node.children { 55 | if let JSXElementChild::JSXFragment(frag) = child { 56 | ctx.add_diagnostic_with_hint(frag.range(), CODE, MESSAGE, HINT); 57 | } 58 | } 59 | } 60 | } 61 | 62 | // most tests are taken from ESlint, commenting those 63 | // requiring code path support 64 | #[cfg(test)] 65 | mod tests { 66 | use super::*; 67 | 68 | #[test] 69 | fn jsx_no_useless_fragment_valid() { 70 | assert_lint_ok! { 71 | JSXNoUselessFragment, 72 | filename: "file:///foo.jsx", 73 | r#"<>
"#, 74 | r#"<>foo
"#, 75 | r#"<>{foo}"#, 76 | r#"<>{foo}bar"#, 77 | }; 78 | } 79 | 80 | #[test] 81 | fn jsx_no_useless_fragment_invalid() { 82 | assert_lint_err! { 83 | JSXNoUselessFragment, 84 | filename: "file:///foo.jsx", 85 | r#"<>"#: [ 86 | { 87 | col: 0, 88 | message: MESSAGE, 89 | hint: HINT, 90 | } 91 | ], 92 | r#"<>
"#: [ 93 | { 94 | col: 0, 95 | message: MESSAGE, 96 | hint: HINT, 97 | } 98 | ], 99 | r#"

foo <>bar

"#: [ 100 | { 101 | col: 7, 102 | message: MESSAGE, 103 | hint: HINT, 104 | } 105 | ], 106 | r#"

foo <>

"#: [ 107 | { 108 | col: 7, 109 | message: MESSAGE, 110 | hint: HINT, 111 | } 112 | ], 113 | }; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/rules/jsx_props_no_spread_multi.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use std::collections::HashSet; 4 | 5 | use super::{Context, LintRule}; 6 | use crate::diagnostic::{LintFix, LintFixChange}; 7 | use crate::handler::{Handler, Traverse}; 8 | use crate::tags::{self, Tags}; 9 | use crate::Program; 10 | use deno_ast::view::{JSXAttrOrSpread, JSXOpeningElement, NodeTrait}; 11 | use deno_ast::{SourceRange, SourceRanged}; 12 | 13 | #[derive(Debug)] 14 | pub struct JSXPropsNoSpreadMulti; 15 | 16 | const CODE: &str = "jsx-props-no-spread-multi"; 17 | 18 | impl LintRule for JSXPropsNoSpreadMulti { 19 | fn tags(&self) -> Tags { 20 | &[tags::RECOMMENDED, tags::REACT, tags::JSX] 21 | } 22 | 23 | fn code(&self) -> &'static str { 24 | CODE 25 | } 26 | 27 | fn lint_program_with_ast_view( 28 | &self, 29 | context: &mut Context, 30 | program: Program, 31 | ) { 32 | JSXPropsNoSpreadMultiHandler.traverse(program, context); 33 | } 34 | } 35 | 36 | const MESSAGE: &str = "Duplicate spread attribute found"; 37 | const HINT: &str = "Remove this spread attribute"; 38 | 39 | struct JSXPropsNoSpreadMultiHandler; 40 | 41 | impl Handler for JSXPropsNoSpreadMultiHandler { 42 | fn jsx_opening_element( 43 | &mut self, 44 | node: &JSXOpeningElement, 45 | ctx: &mut Context, 46 | ) { 47 | let mut seen: HashSet<&str> = HashSet::new(); 48 | for attr in node.attrs { 49 | if let JSXAttrOrSpread::SpreadElement(spread) = attr { 50 | let text = spread.expr.text(); 51 | if seen.contains(text) { 52 | ctx.add_diagnostic_with_fixes( 53 | spread.range(), 54 | CODE, 55 | MESSAGE, 56 | Some(HINT.to_string()), 57 | vec![LintFix { 58 | description: "Remove this spread attribute".into(), 59 | changes: vec![LintFixChange { 60 | new_text: "".into(), 61 | range: SourceRange { 62 | start: attr.range().start - 2, 63 | end: attr.range().end + 1, 64 | }, 65 | }], 66 | }], 67 | ); 68 | } 69 | 70 | seen.insert(text); 71 | } 72 | } 73 | } 74 | } 75 | 76 | // most tests are taken from ESlint, commenting those 77 | // requiring code path support 78 | #[cfg(test)] 79 | mod tests { 80 | use super::*; 81 | 82 | #[test] 83 | fn jsx_props_no_spread_multi_valid() { 84 | assert_lint_ok! { 85 | JSXPropsNoSpreadMulti, 86 | filename: "file:///foo.jsx", 87 | r#"
"#, 88 | r#"
"#, 89 | r#""#, 90 | r#""#, 91 | r#""#, 92 | }; 93 | } 94 | 95 | #[test] 96 | fn jsx_props_no_spread_multi_invalid() { 97 | assert_lint_err! { 98 | JSXPropsNoSpreadMulti, 99 | filename: "file:///foo.jsx", 100 | r#"
"#: [ 101 | { 102 | col: 15, 103 | message: MESSAGE, 104 | hint: HINT, 105 | fix: ( 106 | "Remove this spread attribute", 107 | "
" 108 | ) 109 | } 110 | ], 111 | r#""#: [ 112 | { 113 | col: 15, 114 | message: MESSAGE, 115 | hint: HINT, 116 | fix: ( 117 | "Remove this spread attribute", 118 | "" 119 | ) 120 | } 121 | ], 122 | r#"
"#: [ 123 | { 124 | col: 25, 125 | message: MESSAGE, 126 | hint: HINT, 127 | fix: ( 128 | "Remove this spread attribute", 129 | "
" 130 | ) 131 | } 132 | ], 133 | }; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/rules/jsx_void_dom_elements_no_children.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::view::{JSXElement, JSXElementName}; 8 | use deno_ast::SourceRanged; 9 | 10 | #[derive(Debug)] 11 | pub struct JSXVoidDomElementsNoChildren; 12 | 13 | const CODE: &str = "jsx-void-dom-elements-no-children"; 14 | 15 | impl LintRule for JSXVoidDomElementsNoChildren { 16 | fn tags(&self) -> Tags { 17 | &[tags::RECOMMENDED, tags::REACT, tags::JSX, tags::FRESH] 18 | } 19 | 20 | fn code(&self) -> &'static str { 21 | CODE 22 | } 23 | 24 | fn lint_program_with_ast_view( 25 | &self, 26 | context: &mut Context, 27 | program: Program, 28 | ) { 29 | JSXVoidDomElementsNoChildrenHandler.traverse(program, context); 30 | } 31 | } 32 | 33 | const MESSAGE: &str = "Found one or more unescaped entities in JSX text"; 34 | const HINT: &str = "Escape the '\">} characters respectively"; 35 | 36 | struct JSXVoidDomElementsNoChildrenHandler; 37 | 38 | impl Handler for JSXVoidDomElementsNoChildrenHandler { 39 | fn jsx_element(&mut self, node: &JSXElement, ctx: &mut Context) { 40 | if let JSXElementName::Ident(name) = node.opening.name { 41 | if !node.children.is_empty() 42 | && matches!( 43 | name.sym().as_str(), 44 | "area" 45 | | "base" 46 | | "br" 47 | | "col" 48 | | "embed" 49 | | "hr" 50 | | "img" 51 | | "input" 52 | | "link" 53 | | "meta" 54 | | "param" 55 | | "source" 56 | | "track" 57 | | "wbr" 58 | ) 59 | { 60 | ctx.add_diagnostic_with_hint(node.range(), CODE, MESSAGE, HINT); 61 | } 62 | } 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | 70 | #[test] 71 | fn jsx_void_dom_elements_no_children_valid() { 72 | assert_lint_ok! { 73 | JSXVoidDomElementsNoChildren, 74 | filename: "file:///foo.jsx", 75 | r#"
"#, 76 | r#""#, 77 | }; 78 | } 79 | 80 | #[test] 81 | fn jsx_void_dom_elements_no_children_invalid() { 82 | assert_lint_err! { 83 | JSXVoidDomElementsNoChildren, 84 | filename: "file:///foo.jsx", 85 | r#"foo"#: [ 86 | { 87 | col: 0, 88 | message: MESSAGE, 89 | hint: HINT, 90 | } 91 | ], 92 | r#"foo"#: [ 93 | { 94 | col: 0, 95 | message: MESSAGE, 96 | hint: HINT, 97 | } 98 | ], 99 | r#"
foo
"#: [ 100 | { 101 | col: 0, 102 | message: MESSAGE, 103 | hint: HINT, 104 | } 105 | ], 106 | r#"foo"#: [ 107 | { 108 | col: 0, 109 | message: MESSAGE, 110 | hint: HINT, 111 | } 112 | ], 113 | r#"foo"#: [ 114 | { 115 | col: 0, 116 | message: MESSAGE, 117 | hint: HINT, 118 | } 119 | ], 120 | r#"
foo"#: [ 121 | { 122 | col: 0, 123 | message: MESSAGE, 124 | hint: HINT, 125 | } 126 | ], 127 | r#"foo"#: [ 128 | { 129 | col: 0, 130 | message: MESSAGE, 131 | hint: HINT, 132 | } 133 | ], 134 | r#"foo"#: [ 135 | { 136 | col: 0, 137 | message: MESSAGE, 138 | hint: HINT, 139 | } 140 | ], 141 | r#"foo"#: [ 142 | { 143 | col: 0, 144 | message: MESSAGE, 145 | hint: HINT, 146 | } 147 | ], 148 | r#"foo"#: [ 149 | { 150 | col: 0, 151 | message: MESSAGE, 152 | hint: HINT, 153 | } 154 | ], 155 | r#"foo"#: [ 156 | { 157 | col: 0, 158 | message: MESSAGE, 159 | hint: HINT, 160 | } 161 | ], 162 | r#"foo"#: [ 163 | { 164 | col: 0, 165 | message: MESSAGE, 166 | hint: HINT, 167 | } 168 | ], 169 | r#"foo"#: [ 170 | { 171 | col: 0, 172 | message: MESSAGE, 173 | hint: HINT, 174 | } 175 | ], 176 | r#"foo"#: [ 177 | { 178 | col: 0, 179 | message: MESSAGE, 180 | hint: HINT, 181 | } 182 | ], 183 | }; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/rules/no_array_constructor.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::view::{CallExpr, Callee, Expr, ExprOrSpread, NewExpr}; 8 | use deno_ast::{SourceRange, SourceRanged}; 9 | 10 | #[derive(Debug)] 11 | pub struct NoArrayConstructor; 12 | 13 | const CODE: &str = "no-array-constructor"; 14 | const MESSAGE: &str = "Array Constructor is not allowed"; 15 | const HINT: &str = "Use array literal notation (e.g. []) or single argument specifying array size only (e.g. new Array(5)"; 16 | 17 | impl LintRule for NoArrayConstructor { 18 | fn tags(&self) -> Tags { 19 | &[tags::RECOMMENDED] 20 | } 21 | 22 | fn code(&self) -> &'static str { 23 | CODE 24 | } 25 | 26 | fn lint_program_with_ast_view( 27 | &self, 28 | context: &mut Context, 29 | program: Program, 30 | ) { 31 | NoArrayConstructorHandler.traverse(program, context); 32 | } 33 | } 34 | 35 | fn check_args( 36 | args: Vec<&ExprOrSpread>, 37 | range: SourceRange, 38 | context: &mut Context, 39 | ) { 40 | if args.len() != 1 { 41 | context.add_diagnostic_with_hint(range, CODE, MESSAGE, HINT); 42 | } 43 | } 44 | 45 | struct NoArrayConstructorHandler; 46 | 47 | impl Handler for NoArrayConstructorHandler { 48 | fn new_expr(&mut self, new_expr: &NewExpr, context: &mut Context) { 49 | if let Expr::Ident(ident) = &new_expr.callee { 50 | let name = ident.inner.as_ref(); 51 | if name != "Array" { 52 | return; 53 | } 54 | if new_expr.type_args.is_some() { 55 | return; 56 | } 57 | match &new_expr.args { 58 | Some(args) => { 59 | check_args(args.to_vec(), new_expr.range(), context); 60 | } 61 | None => check_args(vec![], new_expr.range(), context), 62 | }; 63 | } 64 | } 65 | 66 | fn call_expr(&mut self, call_expr: &CallExpr, context: &mut Context) { 67 | if let Callee::Expr(Expr::Ident(ident)) = &call_expr.callee { 68 | let name = ident.inner.as_ref(); 69 | if name != "Array" { 70 | return; 71 | } 72 | if call_expr.type_args.is_some() { 73 | return; 74 | } 75 | 76 | check_args((*call_expr.args).to_vec(), call_expr.range(), context); 77 | } 78 | } 79 | } 80 | 81 | #[cfg(test)] 82 | mod tests { 83 | use super::*; 84 | 85 | #[test] 86 | fn no_array_constructor_valid() { 87 | assert_lint_ok! { 88 | NoArrayConstructor, 89 | "Array(x)", 90 | "Array(9)", 91 | "Array.foo()", 92 | "foo.Array()", 93 | "new Array(x)", 94 | "new Array(9)", 95 | "new foo.Array()", 96 | "new Array.foo", 97 | "new Array(1, 2, 3);", 98 | "new Array()", 99 | "Array(1, 2, 3);", 100 | "Array();", 101 | }; 102 | } 103 | 104 | #[test] 105 | fn no_array_constructor_invalid() { 106 | assert_lint_err! { 107 | NoArrayConstructor, 108 | "new Array": [{ col: 0, message: MESSAGE, hint: HINT }], 109 | "new Array()": [{ col: 0, message: MESSAGE, hint: HINT }], 110 | "new Array(x, y)": [{ col: 0, message: MESSAGE, hint: HINT }], 111 | "new Array(0, 1, 2)": [{ col: 0, message: MESSAGE, hint: HINT }], 112 | // nested 113 | r#" 114 | const a = new class { 115 | foo() { 116 | let arr = new Array(); 117 | } 118 | }(); 119 | "#: [{ line: 4, col: 14, message: MESSAGE, hint: HINT }], 120 | r#" 121 | const a = (() => { 122 | let arr = new Array(); 123 | })(); 124 | "#: [{ line: 3, col: 12, message: MESSAGE, hint: HINT }], 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/rules/no_async_promise_executor.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::view::{Expr, NewExpr, ParenExpr}; 8 | use deno_ast::SourceRanged; 9 | 10 | #[derive(Debug)] 11 | pub struct NoAsyncPromiseExecutor; 12 | 13 | const CODE: &str = "no-async-promise-executor"; 14 | const MESSAGE: &str = "Async promise executors are not allowed"; 15 | const HINT: &str = 16 | "Remove `async` from executor function and adjust promise code as needed"; 17 | 18 | impl LintRule for NoAsyncPromiseExecutor { 19 | fn tags(&self) -> Tags { 20 | &[tags::RECOMMENDED] 21 | } 22 | 23 | fn code(&self) -> &'static str { 24 | CODE 25 | } 26 | 27 | fn lint_program_with_ast_view( 28 | &self, 29 | context: &mut Context, 30 | program: Program, 31 | ) { 32 | NoAsyncPromiseExecutorHandler.traverse(program, context); 33 | } 34 | } 35 | 36 | fn is_async_function(expr: &Expr) -> bool { 37 | match expr { 38 | Expr::Fn(fn_expr) => fn_expr.function.is_async(), 39 | Expr::Arrow(arrow_expr) => arrow_expr.is_async(), 40 | Expr::Paren(ParenExpr { ref expr, .. }) => is_async_function(expr), 41 | _ => false, 42 | } 43 | } 44 | 45 | struct NoAsyncPromiseExecutorHandler; 46 | 47 | impl Handler for NoAsyncPromiseExecutorHandler { 48 | fn new_expr(&mut self, new_expr: &NewExpr, context: &mut Context) { 49 | if let Expr::Ident(ident) = &new_expr.callee { 50 | let name = ident.inner.as_ref(); 51 | if name != "Promise" { 52 | return; 53 | } 54 | 55 | if let Some(args) = &new_expr.args { 56 | if let Some(first_arg) = args.first() { 57 | if is_async_function(&first_arg.expr) { 58 | context.add_diagnostic_with_hint( 59 | new_expr.range(), 60 | CODE, 61 | MESSAGE, 62 | HINT, 63 | ); 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | #[cfg(test)] 72 | mod tests { 73 | use super::*; 74 | 75 | #[test] 76 | fn no_async_promise_executor_valid() { 77 | assert_lint_ok! { 78 | NoAsyncPromiseExecutor, 79 | "new Promise(function(resolve, reject) {});", 80 | "new Promise((resolve, reject) => {});", 81 | "new Promise((resolve, reject) => {}, async function unrelated() {})", 82 | "new Foo(async (resolve, reject) => {})", 83 | "new class { foo() { new Promise(function(resolve, reject) {}); } }", 84 | }; 85 | } 86 | 87 | #[test] 88 | fn no_async_promise_executor_invalid() { 89 | assert_lint_err! { 90 | NoAsyncPromiseExecutor, 91 | "new Promise(async function(resolve, reject) {});": [{ col: 0, message: MESSAGE, hint: HINT }], 92 | "new Promise(async function foo(resolve, reject) {});": [{ col: 0, message: MESSAGE, hint: HINT }], 93 | "new Promise(async (resolve, reject) => {});": [{ col: 0, message: MESSAGE, hint: HINT }], 94 | "new Promise(((((async () => {})))));": [{ col: 0, message: MESSAGE, hint: HINT }], 95 | // nested 96 | r#" 97 | const a = new class { 98 | foo() { 99 | let b = new Promise(async function(resolve, reject) {}); 100 | } 101 | } 102 | "#: [{ line: 4, col: 12, message: MESSAGE, hint: HINT }], 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/rules/no_await_in_sync_fn.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::view::NodeTrait; 8 | use deno_ast::{view as ast_view, SourceRanged}; 9 | 10 | #[derive(Debug)] 11 | pub struct NoAwaitInSyncFn; 12 | 13 | const CODE: &str = "no-await-in-sync-fn"; 14 | const MESSAGE: &str = "Unexpected `await` inside a non-async function."; 15 | const HINT: &str = "Remove `await` in the function body or change the function to an async function."; 16 | 17 | impl LintRule for NoAwaitInSyncFn { 18 | fn tags(&self) -> Tags { 19 | &[tags::RECOMMENDED] 20 | } 21 | 22 | fn code(&self) -> &'static str { 23 | CODE 24 | } 25 | 26 | fn lint_program_with_ast_view( 27 | &self, 28 | context: &mut Context, 29 | program: Program<'_>, 30 | ) { 31 | NoAwaitInSyncFnHandler.traverse(program, context); 32 | } 33 | } 34 | 35 | struct NoAwaitInSyncFnHandler; 36 | 37 | impl Handler for NoAwaitInSyncFnHandler { 38 | fn await_expr( 39 | &mut self, 40 | await_expr: &ast_view::AwaitExpr, 41 | ctx: &mut Context, 42 | ) { 43 | fn inside_sync_fn(node: ast_view::Node) -> bool { 44 | use deno_ast::view::Node::*; 45 | match node { 46 | FnDecl(decl) => !decl.function.is_async(), 47 | FnExpr(decl) => !decl.function.is_async(), 48 | ArrowExpr(decl) => !decl.is_async(), 49 | MethodProp(decl) => !decl.function.is_async(), 50 | ClassMethod(decl) => !decl.function.is_async(), 51 | PrivateMethod(decl) => !decl.function.is_async(), 52 | _ => { 53 | let parent = match node.parent() { 54 | Some(p) => p, 55 | None => return false, 56 | }; 57 | inside_sync_fn(parent) 58 | } 59 | } 60 | } 61 | 62 | if inside_sync_fn(await_expr.as_node()) { 63 | ctx.add_diagnostic_with_hint(await_expr.range(), CODE, MESSAGE, HINT); 64 | } 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use super::*; 71 | 72 | #[test] 73 | fn no_await_in_sync_fn_valid() { 74 | assert_lint_ok! { 75 | NoAwaitInSyncFn, 76 | r#" 77 | async function foo(things) { 78 | await bar(); 79 | } 80 | "#, 81 | r#" 82 | const foo = async (things) => { 83 | await bar(); 84 | } 85 | "#, 86 | r#" 87 | const foo = async function(things) { 88 | await bar(); 89 | } 90 | "#, 91 | r#" 92 | const foo = { 93 | async foo(things) { 94 | await bar(); 95 | } 96 | } 97 | "#, 98 | r#" 99 | class Foo { 100 | async foo(things) { 101 | await bar(); 102 | } 103 | } 104 | "#, 105 | r#" 106 | class Foo { 107 | async #foo(things) { 108 | await bar(); 109 | } 110 | } 111 | "#, 112 | } 113 | } 114 | 115 | #[test] 116 | fn no_await_in_sync_fn_invalid() { 117 | assert_lint_err! { 118 | NoAwaitInSyncFn, 119 | MESSAGE, 120 | HINT, 121 | r#" 122 | function foo(things) { 123 | await bar(); 124 | } 125 | "#: [{ line: 3, col: 8 }], 126 | r#" 127 | const foo = things => { 128 | await bar(); 129 | } 130 | "#: [{ line: 3, col: 8 }], 131 | r#" 132 | const foo = function (things) { 133 | await bar(); 134 | } 135 | "#: [{ line: 3, col: 8 }], 136 | r#" 137 | const foo = { 138 | foo(things) { 139 | await bar(); 140 | } 141 | } 142 | "#: [{ line: 4, col: 10 }], 143 | r#" 144 | class Foo { 145 | foo(things) { 146 | await bar(); 147 | } 148 | } 149 | "#: [{ line: 4, col: 10 }], 150 | r#" 151 | class Foo { 152 | #foo(things) { 153 | await bar(); 154 | } 155 | } 156 | "#: [{ line: 4, col: 10 }], 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/rules/no_boolean_literal_for_arguments.rs: -------------------------------------------------------------------------------- 1 | use super::{Context, LintRule}; 2 | use crate::handler::{Handler, Traverse}; 3 | use crate::tags::Tags; 4 | use crate::Program; 5 | use deno_ast::view::{CallExpr, NodeTrait}; 6 | use deno_ast::SourceRanged; 7 | 8 | #[derive(Debug)] 9 | pub struct NoBooleanLiteralForArguments; 10 | 11 | const CODE: &str = "no-boolean-literal-for-arguments"; 12 | const MESSAGE: &str = "Please create a self-documenting constant instead of \ 13 | passing plain booleans values as arguments"; 14 | const HINT: &str = 15 | "const ARG_ONE = true, ARG_TWO = false;\nyourFunction(ARG_ONE, ARG_TWO)"; 16 | 17 | impl LintRule for NoBooleanLiteralForArguments { 18 | fn lint_program_with_ast_view<'view>( 19 | &self, 20 | context: &mut Context<'view>, 21 | program: Program<'view>, 22 | ) { 23 | NoBooleanLiteralForArgumentsVisitor.traverse(program, context); 24 | } 25 | 26 | fn code(&self) -> &'static str { 27 | CODE 28 | } 29 | 30 | fn tags(&self) -> Tags { 31 | &[] 32 | } 33 | } 34 | 35 | struct NoBooleanLiteralForArgumentsVisitor; 36 | 37 | impl Handler for NoBooleanLiteralForArgumentsVisitor { 38 | fn call_expr(&mut self, call_expression: &CallExpr, ctx: &mut Context) { 39 | let args = call_expression.args; 40 | let is_boolean_literal = 41 | |text: &str| -> bool { matches!(text, "true" | "false") }; 42 | for arg in args { 43 | if is_boolean_literal(arg.text()) { 44 | ctx.add_diagnostic_with_hint( 45 | call_expression.range(), 46 | CODE, 47 | MESSAGE, 48 | HINT, 49 | ); 50 | break; 51 | } 52 | } 53 | } 54 | } 55 | 56 | #[cfg(test)] 57 | mod test { 58 | use super::*; 59 | 60 | #[test] 61 | fn no_boolean_literal_for_arguments_valid() { 62 | assert_lint_ok! { 63 | NoBooleanLiteralForArguments, 64 | r#"runCMDCommand(command, executionMode)"#, 65 | r#" 66 | function formatLog(logData: { level: string, text: string }) { 67 | console.log(`[${level}]:${text}`); 68 | } 69 | formatLog({ level: "INFO", text: "Connected to the DB!" }); 70 | "#, 71 | r#" 72 | function displayInformation(display: { renderer: "terminal" | "screen", recursive: boolean }) { 73 | if (display) { 74 | renderInformation(); 75 | } 76 | // TODO! 77 | } 78 | displayInformation({ renderer: "terminal", recursive: true }); 79 | "# 80 | } 81 | } 82 | 83 | #[test] 84 | fn no_boolean_literal_for_arguments_invalid() { 85 | assert_lint_err! { 86 | NoBooleanLiteralForArguments, 87 | r#"test(true,true)"#:[{line: 1, col: 0, message: MESSAGE, hint: HINT}], 88 | r#"test(false,true)"#:[{line: 1, col: 0, message: MESSAGE, hint: HINT}], 89 | r#"test(false,false)"#:[{line: 1, col: 0, message: MESSAGE, hint: HINT}], 90 | r#"invoke(true,remoteServerUrl,true)"#:[{line: 1, col: 0, message: MESSAGE, hint: HINT}], 91 | r#" 92 | function enableLinting(enable: boolean, limitDepth: boolean) { 93 | if (enable) { 94 | linter.run(); 95 | } 96 | } 97 | enableLinting(true,false); 98 | "#:[{line: 7, col: 6, message: MESSAGE, hint: HINT}], 99 | r#" 100 | runCMD(true, CMD.MODE_ONE) 101 | "#:[{line: 2, col: 6, message: MESSAGE, hint: HINT}], 102 | r#" 103 | function displayInformation(display: boolean) { 104 | if (display) { 105 | renderInformation(); 106 | } 107 | // TODO! 108 | } 109 | displayInformation(true); 110 | "#:[{line: 8, col: 6, message: MESSAGE, hint: HINT}], 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/rules/no_class_assign.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::swc_util::find_lhs_ids; 6 | use crate::tags::{self, Tags}; 7 | use crate::Program; 8 | use deno_ast::view::AssignExpr; 9 | use deno_ast::{BindingKind, SourceRanged}; 10 | 11 | #[derive(Debug)] 12 | pub struct NoClassAssign; 13 | 14 | const CODE: &str = "no-class-assign"; 15 | const MESSAGE: &str = "Reassigning class declaration is not allowed"; 16 | const HINT: &str = "Do you have the right variable here?"; 17 | 18 | impl LintRule for NoClassAssign { 19 | fn tags(&self) -> Tags { 20 | &[tags::RECOMMENDED] 21 | } 22 | 23 | fn code(&self) -> &'static str { 24 | CODE 25 | } 26 | 27 | fn lint_program_with_ast_view<'view>( 28 | &self, 29 | context: &mut Context<'view>, 30 | program: Program<'view>, 31 | ) { 32 | NoClassAssignVisitor.traverse(program, context); 33 | } 34 | } 35 | 36 | struct NoClassAssignVisitor; 37 | 38 | impl Handler for NoClassAssignVisitor { 39 | fn assign_expr(&mut self, assign_expr: &AssignExpr, ctx: &mut Context) { 40 | let ids = find_lhs_ids(&assign_expr.left); 41 | for id in ids { 42 | let var = ctx.scope().var(&id); 43 | if let Some(var) = var { 44 | if let BindingKind::Class = var.kind() { 45 | ctx.add_diagnostic_with_hint( 46 | assign_expr.range(), 47 | CODE, 48 | MESSAGE, 49 | HINT, 50 | ); 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | use super::*; 60 | 61 | // Some tests are derived from 62 | // https://github.com/eslint/eslint/blob/v7.10.0/tests/lib/rules/no-class-assign.js 63 | // MIT Licensed. 64 | 65 | #[test] 66 | fn no_class_assign_valid() { 67 | assert_lint_ok! { 68 | NoClassAssign, 69 | r#"class A {}"#, 70 | r#"class A {} foo(A);"#, 71 | r#"let A = class A {}; foo(A);"#, 72 | r#" 73 | class A { 74 | foo(A) { 75 | A = "foobar"; 76 | } 77 | } 78 | "#, 79 | r#" 80 | class A { 81 | foo() { 82 | let A; 83 | A = "bar"; 84 | } 85 | } 86 | "#, 87 | r#" 88 | let A = class { 89 | b() { 90 | A = 0; 91 | } 92 | } 93 | "#, 94 | r#" 95 | let A, B; 96 | A = class { 97 | b() { 98 | B = 0; 99 | } 100 | } 101 | "#, 102 | r#"let x = 0; x = 1;"#, 103 | r#"var x = 0; x = 1;"#, 104 | r#"const x = 0;"#, 105 | r#"function x() {} x = 1;"#, 106 | r#"function foo(x) { x = 1; }"#, 107 | r#"try {} catch (x) { x = 1; }"#, 108 | }; 109 | } 110 | 111 | #[test] 112 | fn no_class_assign_invalid() { 113 | assert_lint_err! { 114 | NoClassAssign, 115 | r#" 116 | class A {} 117 | A = 0; 118 | "#: [ 119 | { 120 | line: 3, 121 | col: 0, 122 | message: MESSAGE, 123 | hint: HINT, 124 | } 125 | ], 126 | r#" 127 | class A {} 128 | ({A} = 0); 129 | "#: [ 130 | { 131 | line: 3, 132 | col: 1, 133 | message: MESSAGE, 134 | hint: HINT, 135 | } 136 | ], 137 | r#" 138 | class A {} 139 | ({b: A = 0} = {}); 140 | "#: [ 141 | { 142 | line: 3, 143 | col: 1, 144 | message: MESSAGE, 145 | hint: HINT, 146 | } 147 | ], 148 | r#" 149 | A = 0; 150 | class A {} 151 | "#: [ 152 | { 153 | line: 2, 154 | col: 0, 155 | message: MESSAGE, 156 | hint: HINT, 157 | } 158 | ], 159 | r#" 160 | class A { 161 | foo() { 162 | A = 0; 163 | } 164 | } 165 | "#: [ 166 | { 167 | line: 4, 168 | col: 4, 169 | message: MESSAGE, 170 | hint: HINT, 171 | } 172 | ], 173 | r#" 174 | let A = class A { 175 | foo() { 176 | A = 0; 177 | } 178 | } 179 | "#: [ 180 | { 181 | line: 4, 182 | col: 4, 183 | message: MESSAGE, 184 | hint: HINT, 185 | } 186 | ], 187 | r#" 188 | class A {} 189 | A = 10; 190 | A = 20; 191 | "#: [ 192 | { 193 | line: 3, 194 | col: 0, 195 | message: MESSAGE, 196 | hint: HINT, 197 | }, 198 | { 199 | line: 4, 200 | col: 0, 201 | message: MESSAGE, 202 | hint: HINT, 203 | } 204 | ], 205 | r#" 206 | let A; 207 | A = class { 208 | foo() { 209 | class B {} 210 | B = 0; 211 | } 212 | } 213 | "#: [ 214 | { 215 | line: 6, 216 | col: 4, 217 | message: MESSAGE, 218 | hint: HINT, 219 | } 220 | ] 221 | }; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/rules/no_console.rs: -------------------------------------------------------------------------------- 1 | use super::{Context, LintRule}; 2 | use crate::handler::{Handler, Traverse}; 3 | use crate::tags::Tags; 4 | use crate::Program; 5 | 6 | use deno_ast::view as ast_view; 7 | use deno_ast::SourceRanged; 8 | use if_chain::if_chain; 9 | 10 | #[derive(Debug)] 11 | pub struct NoConsole; 12 | 13 | const MESSAGE: &str = "`console` usage is not allowed."; 14 | const CODE: &str = "no-console"; 15 | 16 | impl LintRule for NoConsole { 17 | fn tags(&self) -> Tags { 18 | &[] 19 | } 20 | 21 | fn code(&self) -> &'static str { 22 | CODE 23 | } 24 | 25 | fn lint_program_with_ast_view( 26 | &self, 27 | context: &mut Context, 28 | program: Program, 29 | ) { 30 | NoConsoleHandler.traverse(program, context); 31 | } 32 | } 33 | 34 | struct NoConsoleHandler; 35 | 36 | impl Handler for NoConsoleHandler { 37 | fn member_expr(&mut self, expr: &ast_view::MemberExpr, ctx: &mut Context) { 38 | if expr.parent().is::() { 39 | return; 40 | } 41 | 42 | use deno_ast::view::Expr; 43 | if_chain! { 44 | if let Expr::Ident(ident) = &expr.obj; 45 | if ident.sym() == "console"; 46 | if ctx.scope().is_global(&ident.inner.to_id()); 47 | then { 48 | ctx.add_diagnostic( 49 | ident.range(), 50 | CODE, 51 | MESSAGE, 52 | ); 53 | } 54 | } 55 | } 56 | 57 | fn expr_stmt(&mut self, expr: &ast_view::ExprStmt, ctx: &mut Context) { 58 | use deno_ast::view::Expr; 59 | if_chain! { 60 | if let Expr::Ident(ident) = &expr.expr; 61 | if ident.sym() == "console"; 62 | if ctx.scope().is_global(&ident.inner.to_id()); 63 | then { 64 | ctx.add_diagnostic( 65 | ident.range(), 66 | CODE, 67 | MESSAGE, 68 | ); 69 | } 70 | } 71 | } 72 | } 73 | 74 | #[cfg(test)] 75 | mod tests { 76 | use super::*; 77 | 78 | #[test] 79 | fn console_allowed() { 80 | assert_lint_ok!( 81 | NoConsole, 82 | // ignored 83 | r"// deno-lint-ignore no-console\nconsole.error('Error message');", 84 | // not global 85 | r"const console = { log() {} } console.log('Error message');", 86 | // https://github.com/denoland/deno_lint/issues/1232 87 | "const x: { console: any } = { console: 21 }; x.console", 88 | ); 89 | } 90 | 91 | #[test] 92 | fn no_console_invalid() { 93 | // Test cases where console is present 94 | assert_lint_err!( 95 | NoConsole, 96 | r#"console.log('Debug message');"#: [{ 97 | col: 0, 98 | message: MESSAGE, 99 | }], 100 | r#"if (debug) { console.log('Debugging'); }"#: [{ 101 | col: 13, 102 | message: MESSAGE, 103 | }], 104 | r#"function log() { console.log('Log'); }"#: [{ 105 | col: 17, 106 | message: MESSAGE, 107 | }], 108 | r#"function log() { console.debug('Log'); }"#: [{ 109 | col: 17, 110 | message: MESSAGE, 111 | }], 112 | r#"console;"#: [{ 113 | col: 0, 114 | message: MESSAGE, 115 | }], 116 | r#"console.warn("test");"#: [{ 117 | col: 0, 118 | message: MESSAGE, 119 | }], 120 | ); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/rules/no_debugger.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::view::DebuggerStmt; 8 | use deno_ast::SourceRanged; 9 | use derive_more::Display; 10 | 11 | #[derive(Debug)] 12 | pub struct NoDebugger; 13 | 14 | const CODE: &str = "no-debugger"; 15 | 16 | #[derive(Display)] 17 | enum NoDebuggerMessage { 18 | #[display(fmt = "`debugger` statement is not allowed")] 19 | Unexpected, 20 | } 21 | 22 | #[derive(Display)] 23 | enum NoDebuggerHint { 24 | #[display(fmt = "Remove the `debugger` statement")] 25 | Remove, 26 | } 27 | 28 | impl LintRule for NoDebugger { 29 | fn tags(&self) -> Tags { 30 | &[tags::RECOMMENDED] 31 | } 32 | 33 | fn code(&self) -> &'static str { 34 | CODE 35 | } 36 | 37 | fn lint_program_with_ast_view( 38 | &self, 39 | context: &mut Context, 40 | program: Program, 41 | ) { 42 | NoDebuggerHandler.traverse(program, context); 43 | } 44 | } 45 | 46 | struct NoDebuggerHandler; 47 | 48 | impl Handler for NoDebuggerHandler { 49 | fn debugger_stmt(&mut self, debugger_stmt: &DebuggerStmt, ctx: &mut Context) { 50 | ctx.add_diagnostic_with_hint( 51 | debugger_stmt.range(), 52 | CODE, 53 | NoDebuggerMessage::Unexpected, 54 | NoDebuggerHint::Remove, 55 | ); 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use super::*; 62 | 63 | #[test] 64 | fn no_debugger_invalid() { 65 | assert_lint_err! { 66 | NoDebugger, 67 | r#"function asdf(): number { console.log("asdf"); debugger; return 1; }"#: [ 68 | { 69 | col: 47, 70 | message: NoDebuggerMessage::Unexpected, 71 | hint: NoDebuggerHint::Remove, 72 | } 73 | ] 74 | }; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/rules/no_delete_var.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::view::{Expr, UnaryExpr, UnaryOp}; 8 | use deno_ast::SourceRanged; 9 | use derive_more::Display; 10 | 11 | #[derive(Debug)] 12 | pub struct NoDeleteVar; 13 | 14 | const CODE: &str = "no-delete-var"; 15 | 16 | #[derive(Display)] 17 | enum NoDeleteVarMessage { 18 | #[display(fmt = "Variables shouldn't be deleted")] 19 | Unexpected, 20 | } 21 | 22 | #[derive(Display)] 23 | enum NoDeleteVarHint { 24 | #[display(fmt = "Remove the deletion statement")] 25 | Remove, 26 | } 27 | 28 | impl LintRule for NoDeleteVar { 29 | fn tags(&self) -> Tags { 30 | &[tags::RECOMMENDED] 31 | } 32 | 33 | fn code(&self) -> &'static str { 34 | CODE 35 | } 36 | 37 | fn lint_program_with_ast_view( 38 | &self, 39 | context: &mut Context, 40 | program: Program, 41 | ) { 42 | NoDeleteVarHandler.traverse(program, context); 43 | } 44 | } 45 | 46 | struct NoDeleteVarHandler; 47 | 48 | impl Handler for NoDeleteVarHandler { 49 | fn unary_expr(&mut self, unary_expr: &UnaryExpr, ctx: &mut Context) { 50 | if unary_expr.op() != UnaryOp::Delete { 51 | return; 52 | } 53 | 54 | if let Expr::Ident(_) = unary_expr.arg { 55 | ctx.add_diagnostic_with_hint( 56 | unary_expr.range(), 57 | CODE, 58 | NoDeleteVarMessage::Unexpected, 59 | NoDeleteVarHint::Remove, 60 | ); 61 | } 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::*; 68 | 69 | #[test] 70 | fn no_delete_var_invalid() { 71 | assert_lint_err! { 72 | NoDeleteVar, 73 | r#"var someVar = "someVar"; delete someVar;"#: [ 74 | { 75 | col: 25, 76 | message: NoDeleteVarMessage::Unexpected, 77 | hint: NoDeleteVarHint::Remove, 78 | } 79 | ], 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/rules/no_empty_character_class.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::view::Regex; 8 | use deno_ast::SourceRanged; 9 | use once_cell::sync::Lazy; 10 | 11 | #[derive(Debug)] 12 | pub struct NoEmptyCharacterClass; 13 | 14 | const CODE: &str = "no-empty-character-class"; 15 | const MESSAGE: &str = "empty character class in RegExp is not allowed"; 16 | const HINT: &str = 17 | "Remove or rework the empty character class (`[]`) in the RegExp"; 18 | 19 | impl LintRule for NoEmptyCharacterClass { 20 | fn tags(&self) -> Tags { 21 | &[tags::RECOMMENDED] 22 | } 23 | 24 | fn code(&self) -> &'static str { 25 | CODE 26 | } 27 | 28 | fn lint_program_with_ast_view( 29 | &self, 30 | context: &mut Context, 31 | program: Program, 32 | ) { 33 | NoEmptyCharacterClassVisitor.traverse(program, context); 34 | } 35 | } 36 | 37 | struct NoEmptyCharacterClassVisitor; 38 | 39 | impl Handler for NoEmptyCharacterClassVisitor { 40 | fn regex(&mut self, regex: &Regex, ctx: &mut Context) { 41 | let raw_regex = regex.text_fast(ctx.text_info()); 42 | 43 | static RULE_REGEX: Lazy = Lazy::new(|| { 44 | /* reference : [eslint no-empty-character-class](https://github.com/eslint/eslint/blob/master/lib/rules/no-empty-character-class.js#L13) 45 | * plain-English description of the following regexp: 46 | * 0. `^` fix the match at the beginning of the string 47 | * 1. `\/`: the `/` that begins the regexp 48 | * 2. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following 49 | * 2.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes) 50 | * 2.1. `\\.`: an escape sequence 51 | * 2.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty 52 | * 3. `\/` the `/` that ends the regexp 53 | * 4. `[dgimsuvy]*`: optional regexp flags 54 | * 5. `$`: fix the match at the end of the string 55 | */ 56 | regex::Regex::new(r"(?u)^/([^\\\[]|\\.|\[([^\\\]]|\\.)+\])*/[dgimsuvy]*$") 57 | .unwrap() 58 | }); 59 | 60 | if !RULE_REGEX.is_match(raw_regex) { 61 | ctx.add_diagnostic_with_hint(regex.range(), CODE, MESSAGE, HINT); 62 | } 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | 70 | #[test] 71 | fn no_empty_character_class_valid() { 72 | assert_lint_ok! { 73 | NoEmptyCharacterClass, 74 | r#" 75 | const foo = /^abc[a-zA-Z]/; 76 | const regExp = new RegExp("^abc[]"); 77 | const foo = /^abc/; 78 | const foo = /[\\[]/; 79 | const foo = /[\\]]/; 80 | const foo = /[a-zA-Z\\[]/; 81 | const foo = /[[]/; 82 | const foo = /[\\[a-z[]]/; 83 | const foo = /[\-\[\]\/\{\}\(\)\*\+\?\.\\^\$\|]/g; 84 | const foo = /\[/g; 85 | const foo = /\]/i; 86 | const foo = /\]/dgimsuvy; 87 | "#, 88 | }; 89 | } 90 | 91 | #[test] 92 | fn no_empty_character_invalid() { 93 | assert_lint_err! { 94 | NoEmptyCharacterClass, 95 | r"const foo = /^abc[]/;": [{ 96 | col: 12, 97 | message: MESSAGE, 98 | hint: HINT, 99 | }], 100 | r"const foo = /foo[]bar/;": [{ 101 | col: 12, 102 | message: MESSAGE, 103 | hint: HINT, 104 | }], 105 | r"const foo = /[]]/;": [{ 106 | col: 12, 107 | message: MESSAGE, 108 | hint: HINT, 109 | }], 110 | r"const foo = /\[[]/;": [{ 111 | col: 12, 112 | message: MESSAGE, 113 | hint: HINT, 114 | }], 115 | r"const foo = /\\[\\[\\]a-z[]/;": [{ 116 | col: 12, 117 | message: MESSAGE, 118 | hint: HINT, 119 | }], 120 | r#"/^abc[]/.test("abcdefg");"#: [{ 121 | col: 0, 122 | message: MESSAGE, 123 | hint: HINT, 124 | }], 125 | r#"if (foo.match(/^abc[]/)) {}"#: [{ 126 | col: 14, 127 | message: MESSAGE, 128 | hint: HINT, 129 | }], 130 | r#""abcdefg".match(/^abc[]/);"#: [{ 131 | col: 16, 132 | message: MESSAGE, 133 | hint: HINT, 134 | }], 135 | r#"if (/^abc[]/.test(foo)) {}"#: [{ 136 | col: 4, 137 | message: MESSAGE, 138 | hint: HINT, 139 | }], 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/rules/no_empty_enum.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::{view as ast_view, SourceRanged}; 8 | 9 | #[derive(Debug)] 10 | pub struct NoEmptyEnum; 11 | 12 | const CODE: &str = "no-empty-enum"; 13 | const MESSAGE: &str = "An empty enum is equivalent to `{}`. Remove this enum or add members to this enum."; 14 | 15 | impl LintRule for NoEmptyEnum { 16 | fn tags(&self) -> Tags { 17 | &[tags::RECOMMENDED] 18 | } 19 | 20 | fn code(&self) -> &'static str { 21 | CODE 22 | } 23 | 24 | fn lint_program_with_ast_view( 25 | &self, 26 | context: &mut Context, 27 | program: Program<'_>, 28 | ) { 29 | NoEmptyEnumHandler.traverse(program, context); 30 | } 31 | } 32 | 33 | struct NoEmptyEnumHandler; 34 | 35 | impl Handler for NoEmptyEnumHandler { 36 | fn ts_enum_decl( 37 | &mut self, 38 | enum_decl: &ast_view::TsEnumDecl, 39 | ctx: &mut Context, 40 | ) { 41 | if enum_decl.members.is_empty() { 42 | ctx.add_diagnostic(enum_decl.range(), CODE, MESSAGE); 43 | } 44 | } 45 | } 46 | 47 | #[cfg(test)] 48 | mod tests { 49 | use super::*; 50 | 51 | #[test] 52 | fn no_empty_enum_valid() { 53 | assert_lint_ok! { 54 | NoEmptyEnum, 55 | "enum Foo { ONE = 'ONE', TWO = 'TWO' }", 56 | "const enum Foo { ONE = 'ONE' }", 57 | }; 58 | } 59 | 60 | #[test] 61 | fn no_empty_enum_invalid() { 62 | assert_lint_err! { 63 | NoEmptyEnum, 64 | "enum Foo {}": [ 65 | { 66 | col: 0, 67 | message: MESSAGE, 68 | } 69 | ], 70 | "const enum Foo {}": [ 71 | { 72 | col: 0, 73 | message: MESSAGE, 74 | } 75 | ], 76 | r#" 77 | enum Foo { 78 | One = 1, 79 | Two = (() => { 80 | enum Bar {} 81 | return 42; 82 | })(), 83 | } 84 | "#: [ 85 | { 86 | line: 5, 87 | col: 4, 88 | message: MESSAGE, 89 | } 90 | ], 91 | "export enum Foo {}": [ 92 | { 93 | col: 7, 94 | message: MESSAGE, 95 | } 96 | ], 97 | "export const enum Foo {}": [ 98 | { 99 | col: 7, 100 | message: MESSAGE, 101 | } 102 | ] 103 | }; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/rules/no_empty_interface.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::view::TsInterfaceDecl; 8 | use deno_ast::SourceRanged; 9 | use derive_more::Display; 10 | 11 | #[derive(Debug)] 12 | pub struct NoEmptyInterface; 13 | 14 | const CODE: &str = "no-empty-interface"; 15 | 16 | #[derive(Display)] 17 | enum NoEmptyInterfaceMessage { 18 | #[display(fmt = "An empty interface is equivalent to `{{}}`.")] 19 | EmptyObject, 20 | } 21 | 22 | #[derive(Display)] 23 | enum NoEmptyInterfaceHint { 24 | #[display(fmt = "Remove this interface or add members to this interface.")] 25 | RemoveOrAddMember, 26 | } 27 | 28 | impl LintRule for NoEmptyInterface { 29 | fn tags(&self) -> Tags { 30 | &[tags::RECOMMENDED] 31 | } 32 | 33 | fn code(&self) -> &'static str { 34 | CODE 35 | } 36 | 37 | fn lint_program_with_ast_view( 38 | &self, 39 | context: &mut Context, 40 | program: Program, 41 | ) { 42 | NoEmptyInterfaceHandler.traverse(program, context); 43 | } 44 | } 45 | 46 | struct NoEmptyInterfaceHandler; 47 | 48 | impl Handler for NoEmptyInterfaceHandler { 49 | fn ts_interface_decl( 50 | &mut self, 51 | interface_decl: &TsInterfaceDecl, 52 | ctx: &mut Context, 53 | ) { 54 | if interface_decl.extends.is_empty() && interface_decl.body.body.is_empty() 55 | { 56 | ctx.add_diagnostic_with_hint( 57 | interface_decl.range(), 58 | CODE, 59 | NoEmptyInterfaceMessage::EmptyObject, 60 | NoEmptyInterfaceHint::RemoveOrAddMember, 61 | ); 62 | } 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | 70 | #[test] 71 | fn no_empty_interface_valid() { 72 | assert_lint_ok! { 73 | NoEmptyInterface, 74 | "interface Foo { a: string }", 75 | "interface Foo { a: number }", 76 | 77 | // This is valid, because: 78 | // - `Bar` can be a type, this makes it so `Foo` has the same members 79 | // as `Bar` but is an interface instead. Behaviour of types and interfaces 80 | // isn't always the same. 81 | // - `Foo` interface might already exist and extend it by the `Bar` members. 82 | "interface Foo extends Bar {}", 83 | 84 | // This is valid because an interface with more than one supertype 85 | // can be used as a replacement of a union type. 86 | "interface Foo extends Bar, Baz {}", 87 | }; 88 | } 89 | 90 | #[test] 91 | fn no_empty_interface_invalid() { 92 | assert_lint_err! { 93 | NoEmptyInterface, 94 | "interface Foo {}": [ 95 | { 96 | col: 0, 97 | message: NoEmptyInterfaceMessage::EmptyObject, 98 | hint: NoEmptyInterfaceHint::RemoveOrAddMember, 99 | } 100 | ], 101 | "interface Foo extends {}": [ 102 | { 103 | col: 0, 104 | message: NoEmptyInterfaceMessage::EmptyObject, 105 | hint: NoEmptyInterfaceHint::RemoveOrAddMember, 106 | } 107 | ], 108 | }; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/rules/no_eval.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::swc_util::StringRepr; 6 | use crate::Program; 7 | use deno_ast::view::{CallExpr, Callee, Expr, ParenExpr, VarDeclarator}; 8 | use deno_ast::{SourceRange, SourceRanged}; 9 | 10 | #[derive(Debug)] 11 | pub struct NoEval; 12 | 13 | const CODE: &str = "no-eval"; 14 | const MESSAGE: &str = "`eval` call is not allowed"; 15 | const HINT: &str = "Remove the use of `eval`"; 16 | 17 | impl LintRule for NoEval { 18 | fn code(&self) -> &'static str { 19 | CODE 20 | } 21 | 22 | fn lint_program_with_ast_view( 23 | &self, 24 | context: &mut Context, 25 | program: Program, 26 | ) { 27 | NoEvalHandler.traverse(program, context); 28 | } 29 | } 30 | 31 | struct NoEvalHandler; 32 | 33 | impl NoEvalHandler { 34 | fn maybe_add_diagnostic( 35 | &mut self, 36 | source: &dyn StringRepr, 37 | range: SourceRange, 38 | ctx: &mut Context, 39 | ) { 40 | if source.string_repr().as_deref() == Some("eval") { 41 | self.add_diagnostic(range, ctx); 42 | } 43 | } 44 | 45 | fn add_diagnostic(&mut self, range: SourceRange, ctx: &mut Context) { 46 | ctx.add_diagnostic_with_hint(range, CODE, MESSAGE, HINT); 47 | } 48 | 49 | fn handle_paren_callee(&mut self, p: &ParenExpr, ctx: &mut Context) { 50 | match p.expr { 51 | // Nested paren callee ((eval))('var foo = 0;') 52 | Expr::Paren(paren) => self.handle_paren_callee(paren, ctx), 53 | // Single argument callee: (eval)('var foo = 0;') 54 | Expr::Ident(ident) => { 55 | self.maybe_add_diagnostic(ident, ident.range(), ctx) 56 | } 57 | // Multiple arguments callee: (0, eval)('var foo = 0;') 58 | Expr::Seq(seq) => { 59 | for expr in seq.exprs { 60 | if let Expr::Ident(ident) = expr { 61 | self.maybe_add_diagnostic(*ident, ident.range(), ctx) 62 | } 63 | } 64 | } 65 | _ => {} 66 | } 67 | } 68 | } 69 | 70 | impl Handler for NoEvalHandler { 71 | fn var_declarator(&mut self, v: &VarDeclarator, ctx: &mut Context) { 72 | if let Some(Expr::Ident(ident)) = &v.init { 73 | self.maybe_add_diagnostic(*ident, v.range(), ctx); 74 | } 75 | } 76 | 77 | fn call_expr(&mut self, call_expr: &CallExpr, ctx: &mut Context) { 78 | if let Callee::Expr(expr) = &call_expr.callee { 79 | match expr { 80 | Expr::Ident(ident) => { 81 | self.maybe_add_diagnostic(*ident, call_expr.range(), ctx) 82 | } 83 | Expr::Paren(paren) => self.handle_paren_callee(paren, ctx), 84 | _ => {} 85 | } 86 | } 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use super::*; 93 | 94 | #[test] 95 | fn no_eval_valid() { 96 | assert_lint_ok! { 97 | NoEval, 98 | "foo.eval('bar');", 99 | } 100 | } 101 | 102 | #[test] 103 | fn no_eval_invalid() { 104 | assert_lint_err! { 105 | NoEval, 106 | "eval('123');": [{col: 0, message: MESSAGE, hint: HINT}], 107 | "(0, eval)('var a = 0');": [{col: 4, message: MESSAGE, hint: HINT}], 108 | "((eval))('var a = 0');": [{col: 2, message: MESSAGE, hint: HINT}], 109 | "var foo = eval;": [{col: 4, message: MESSAGE, hint: HINT}], 110 | 111 | // TODO (see: https://github.com/denoland/deno_lint/pull/490) 112 | // "this.eval("123");": [{col: 0, message: MESSAGE, hint: HINT}], 113 | // "var foo = this.eval;": [{col: 0, message: MESSAGE, hint: HINT}], 114 | // "(function(exe){ exe('foo') })(eval);": [{col: 0, message: MESSAGE, hint: HINT}], 115 | // 116 | // "(0, window.eval)('foo');": [{col: 0, message: MESSAGE, hint: HINT}], 117 | // "(0, window['eval'])('foo');": [{col: 0, message: MESSAGE, hint: HINT}], 118 | // "var foo = window.eval;": [{col: 0, message: MESSAGE, hint: HINT}], 119 | // "window.eval('foo');": [{col: 0, message: MESSAGE, hint: HINT}], 120 | // "window.window.eval('foo');": [{col: 0, message: MESSAGE, hint: HINT}], 121 | // "window.window['eval']('foo');": [{col: 0, message: MESSAGE, hint: HINT}], 122 | // 123 | // "var foo = globalThis.eval;": [{col: 0, message: MESSAGE, hint: HINT}], 124 | // "globalThis.eval('foo')": [{col: 0, message: MESSAGE, hint: HINT}], 125 | // "globalThis.globalThis.eval('foo')": [{col: 0, message: MESSAGE, hint: HINT}], 126 | // "globalThis.globalThis['eval']('foo')": [{col: 0, message: MESSAGE, hint: HINT}], 127 | // "(0, globalThis.eval)('foo')": [{col: 0, message: MESSAGE, hint: HINT}], 128 | // "(0, globalThis['eval'])('foo')": [{col: 0, message: MESSAGE, hint: HINT}], 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/rules/no_explicit_any.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::swc::ast::TsKeywordTypeKind::TsAnyKeyword; 8 | use deno_ast::view::TsKeywordType; 9 | use deno_ast::SourceRanged; 10 | 11 | #[derive(Debug)] 12 | pub struct NoExplicitAny; 13 | 14 | const CODE: &str = "no-explicit-any"; 15 | const MESSAGE: &str = "`any` type is not allowed"; 16 | const HINT: &str = "Use a specific type other than `any`"; 17 | 18 | impl LintRule for NoExplicitAny { 19 | fn tags(&self) -> Tags { 20 | &[tags::RECOMMENDED] 21 | } 22 | 23 | fn code(&self) -> &'static str { 24 | CODE 25 | } 26 | 27 | fn lint_program_with_ast_view( 28 | &self, 29 | context: &mut Context, 30 | program: Program, 31 | ) { 32 | NoExplicitAnyHandler.traverse(program, context); 33 | } 34 | } 35 | 36 | struct NoExplicitAnyHandler; 37 | 38 | impl Handler for NoExplicitAnyHandler { 39 | fn ts_keyword_type( 40 | &mut self, 41 | ts_keyword_type: &TsKeywordType, 42 | ctx: &mut Context, 43 | ) { 44 | if ts_keyword_type.keyword_kind() == TsAnyKeyword { 45 | ctx.add_diagnostic_with_hint( 46 | ts_keyword_type.range(), 47 | CODE, 48 | MESSAGE, 49 | HINT, 50 | ); 51 | } 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | 59 | #[test] 60 | fn no_explicit_any_valid() { 61 | assert_lint_ok! { 62 | NoExplicitAny, 63 | r#" 64 | class Foo { 65 | static _extensions: { 66 | // deno-lint-ignore no-explicit-any 67 | [key: string]: (module: Module, filename: string) => any; 68 | } = Object.create(null); 69 | }"#, 70 | r#" 71 | type RequireWrapper = ( 72 | // deno-lint-ignore no-explicit-any 73 | exports: any, 74 | // deno-lint-ignore no-explicit-any 75 | require: any, 76 | module: Module, 77 | __filename: string, 78 | __dirname: string 79 | ) => void;"#, 80 | }; 81 | } 82 | 83 | #[test] 84 | fn no_explicit_any_invalid() { 85 | assert_lint_err! { 86 | NoExplicitAny, 87 | "function foo(): any { return undefined; }": [{ col: 16, message: MESSAGE, hint: HINT }], 88 | "function bar(): Promise { return undefined; }": [{ col: 24, message: MESSAGE, hint: HINT }], 89 | "const a: any = {};": [{ col: 9, message: MESSAGE, hint: HINT }], 90 | r#" 91 | class Foo { 92 | static _extensions: { 93 | [key: string]: (module: Module, filename: string) => any; 94 | } = Object.create(null); 95 | }"#: [{ line: 4, col: 57, message: MESSAGE, hint: HINT }], 96 | r#" 97 | type RequireWrapper = ( 98 | exports: any, 99 | require: any, 100 | module: Module, 101 | __filename: string, 102 | __dirname: string 103 | ) => void;"#: [{ line: 3, col: 11, message: MESSAGE, hint: HINT }, { line: 4, col: 11, message: MESSAGE, hint: HINT }], 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/rules/no_external_imports.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::Tags; 6 | use crate::Program; 7 | use deno_ast::view::ImportDecl; 8 | use deno_ast::{ModuleSpecifier, SourceRanged}; 9 | use derive_more::Display; 10 | use std::ffi::OsStr; 11 | 12 | #[derive(Debug)] 13 | pub struct NoExternalImport; 14 | 15 | const CODE: &str = "no-external-import"; 16 | 17 | #[derive(Display)] 18 | enum NoExternalImportMessage { 19 | #[display(fmt = "Not allowed to import external resources")] 20 | Unexpected, 21 | } 22 | 23 | #[derive(Display)] 24 | enum NoExternalImportHint { 25 | #[display(fmt = "Create a deps.ts file or use import maps")] 26 | CreateDependencyFile, 27 | } 28 | 29 | impl LintRule for NoExternalImport { 30 | fn tags(&self) -> Tags { 31 | &[] 32 | } 33 | 34 | fn code(&self) -> &'static str { 35 | CODE 36 | } 37 | 38 | fn lint_program_with_ast_view( 39 | &self, 40 | context: &mut Context, 41 | program: Program, 42 | ) { 43 | let mut handler = NoExternalImportHandler; 44 | handler.traverse(program, context); 45 | } 46 | } 47 | 48 | #[derive(Default)] 49 | struct NoExternalImportHandler; 50 | 51 | impl NoExternalImportHandler { 52 | fn check_import_path(&self, decl: &ImportDecl, ctx: &mut Context) { 53 | let parsed_src = ModuleSpecifier::parse(decl.src.value()); 54 | let maybe_file_path = ctx.specifier().to_file_path().ok(); 55 | let file_stem = maybe_file_path 56 | .as_ref() 57 | .and_then(|p| p.file_stem()) 58 | .and_then(OsStr::to_str); 59 | 60 | if parsed_src.is_ok() && file_stem != Some("deps") { 61 | ctx.add_diagnostic_with_hint( 62 | decl.range(), 63 | CODE, 64 | NoExternalImportMessage::Unexpected, 65 | NoExternalImportHint::CreateDependencyFile, 66 | ); 67 | } 68 | } 69 | } 70 | 71 | impl Handler for NoExternalImportHandler { 72 | fn import_decl( 73 | &mut self, 74 | decl: &deno_ast::view::ImportDecl, 75 | ctx: &mut Context, 76 | ) { 77 | self.check_import_path(decl, ctx); 78 | } 79 | } 80 | 81 | #[cfg(test)] 82 | mod tests { 83 | use super::*; 84 | 85 | #[test] 86 | fn no_external_import_valid() { 87 | assert_lint_ok! { 88 | NoExternalImport, 89 | "import { assertEquals } from './deps.ts'", 90 | "import { assertEquals } from 'deps.ts'", 91 | "import Foo from './deps.ts';", 92 | "import type { Foo } from './deps.ts';", 93 | "import type Foo from './deps.ts';", 94 | "import * as Foo from './deps.ts';", 95 | "import './deps.ts';", 96 | "const foo = await import('https://example.com');" 97 | }; 98 | 99 | assert_lint_ok! { 100 | NoExternalImport, 101 | filename: if cfg!(windows) { 102 | "file:///c:/deps.ts" 103 | } else { 104 | "file:///deps.ts" 105 | }, 106 | "import { assertEquals } from 'https://deno.land/std@0.126.0/testing/asserts.ts'" 107 | }; 108 | } 109 | 110 | #[test] 111 | fn no_external_import_invalid() { 112 | assert_lint_err! { 113 | NoExternalImport, 114 | "import { assertEquals } from 'https://deno.land/std@0.126.0/testing/asserts.ts'": [ 115 | { 116 | col: 0, 117 | message: NoExternalImportMessage::Unexpected, 118 | hint: NoExternalImportHint::CreateDependencyFile, 119 | }, 120 | ], 121 | "import assertEquals from 'http://deno.land/std@0.126.0/testing/asserts.ts'": [ 122 | { 123 | col: 0, 124 | message: NoExternalImportMessage::Unexpected, 125 | hint: NoExternalImportHint::CreateDependencyFile, 126 | }, 127 | ], 128 | "import type { Foo } from 'https://example.com';": [ 129 | { 130 | col: 0, 131 | message: NoExternalImportMessage::Unexpected, 132 | hint: NoExternalImportHint::CreateDependencyFile, 133 | }, 134 | ], 135 | "import type Foo from 'https://example.com';": [ 136 | { 137 | col: 0, 138 | message: NoExternalImportMessage::Unexpected, 139 | hint: NoExternalImportHint::CreateDependencyFile, 140 | }, 141 | ], 142 | "import * as Foo from 'https://example.com';": [ 143 | { 144 | col: 0, 145 | message: NoExternalImportMessage::Unexpected, 146 | hint: NoExternalImportHint::CreateDependencyFile, 147 | }, 148 | ], 149 | "import 'https://example.com';": [ 150 | { 151 | col: 0, 152 | message: NoExternalImportMessage::Unexpected, 153 | hint: NoExternalImportHint::CreateDependencyFile, 154 | }, 155 | ], 156 | }; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/rules/no_global_assign.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use crate::{globals::GLOBALS, swc_util::find_lhs_ids}; 8 | use deno_ast::swc::ast::Id; 9 | use deno_ast::SourceRange; 10 | use deno_ast::SourceRangedForSpanned; 11 | use deno_ast::{view::*, SourceRanged}; 12 | use derive_more::Display; 13 | 14 | #[derive(Debug)] 15 | pub struct NoGlobalAssign; 16 | 17 | const CODE: &str = "no-global-assign"; 18 | 19 | #[derive(Display)] 20 | enum NoGlobalAssignMessage { 21 | #[display(fmt = "Assignment to global is not allowed")] 22 | NotAllowed, 23 | } 24 | 25 | #[derive(Display)] 26 | enum NoGlobalAssignHint { 27 | #[display(fmt = "Remove the assignment to the global variable")] 28 | Remove, 29 | } 30 | 31 | impl LintRule for NoGlobalAssign { 32 | fn tags(&self) -> Tags { 33 | &[tags::RECOMMENDED] 34 | } 35 | 36 | fn code(&self) -> &'static str { 37 | CODE 38 | } 39 | 40 | fn lint_program_with_ast_view<'view>( 41 | &self, 42 | context: &mut Context<'view>, 43 | program: Program<'view>, 44 | ) { 45 | NoGlobalAssignVisitor.traverse(program, context) 46 | } 47 | } 48 | 49 | struct NoGlobalAssignVisitor; 50 | 51 | impl NoGlobalAssignVisitor { 52 | fn check(&mut self, range: SourceRange, id: Id, ctx: &mut Context) { 53 | if id.1 != ctx.unresolved_ctxt() { 54 | return; 55 | } 56 | 57 | if ctx.scope().var(&id).is_some() { 58 | return; 59 | } 60 | 61 | // We only care about globals. 62 | let maybe_global = GLOBALS.iter().find(|(name, _)| name == &&*id.0); 63 | 64 | if let Some(global) = maybe_global { 65 | // If global can be overwritten then don't need to report anything 66 | if !global.1 { 67 | ctx.add_diagnostic_with_hint( 68 | range, 69 | CODE, 70 | NoGlobalAssignMessage::NotAllowed, 71 | NoGlobalAssignHint::Remove, 72 | ); 73 | } 74 | } 75 | } 76 | } 77 | 78 | impl Handler for NoGlobalAssignVisitor { 79 | fn assign_expr(&mut self, e: &AssignExpr, ctx: &mut Context) { 80 | let idents: Vec = find_lhs_ids(&e.left); 81 | 82 | for ident in idents { 83 | self.check(ident.range(), ident.to_id(), ctx); 84 | } 85 | } 86 | 87 | fn update_expr(&mut self, e: &UpdateExpr, ctx: &mut Context) { 88 | if let Expr::Ident(i) = e.arg { 89 | self.check(e.range(), i.to_id(), ctx); 90 | } 91 | } 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | use super::*; 97 | 98 | #[test] 99 | fn no_global_assign_valid() { 100 | assert_lint_ok! { 101 | NoGlobalAssign, 102 | "string = 'hello world';", 103 | "var string;", 104 | "top = 0;", 105 | "require = 0;", 106 | "onmessage = function () {};", 107 | "let Array = 0; Array = 42;", 108 | r#" 109 | let Boolean = true; 110 | function foo() { 111 | Boolean = false; 112 | } 113 | "#, 114 | }; 115 | } 116 | 117 | #[test] 118 | fn no_global_assign_invalid() { 119 | assert_lint_err! { 120 | NoGlobalAssign, 121 | "String = 'hello world';": [ 122 | { 123 | col: 0, 124 | message: NoGlobalAssignMessage::NotAllowed, 125 | hint: NoGlobalAssignHint::Remove, 126 | } 127 | ], 128 | "String++;": [ 129 | { 130 | col: 0, 131 | message: NoGlobalAssignMessage::NotAllowed, 132 | hint: NoGlobalAssignHint::Remove, 133 | } 134 | ], 135 | "({Object = 0, String = 0} = {});": [ 136 | { 137 | col: 2, 138 | message: NoGlobalAssignMessage::NotAllowed, 139 | hint: NoGlobalAssignHint::Remove, 140 | }, 141 | { 142 | col: 14, 143 | message: NoGlobalAssignMessage::NotAllowed, 144 | hint: NoGlobalAssignHint::Remove, 145 | } 146 | ], 147 | "Array = 1;": [ 148 | { 149 | col: 0, 150 | message: NoGlobalAssignMessage::NotAllowed, 151 | hint: NoGlobalAssignHint::Remove, 152 | } 153 | ], 154 | r#" 155 | function foo() { 156 | let Boolean = false; 157 | Boolean = true; 158 | } 159 | Boolean = true; 160 | "#: [ 161 | { 162 | col: 0, 163 | line: 6, 164 | message: NoGlobalAssignMessage::NotAllowed, 165 | hint: NoGlobalAssignHint::Remove, 166 | }, 167 | ], 168 | }; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/rules/no_implicit_declare_namespace_export.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::Tags; 6 | use crate::Program; 7 | use deno_ast::{view as ast_view, SourceRanged}; 8 | 9 | #[derive(Debug)] 10 | pub struct NoImplicitDeclareNamespaceExport; 11 | 12 | const CODE: &str = "no-implicit-declare-namespace-export"; 13 | const MESSAGE: &str = 14 | "Implicit exports in ambient namespaces are discouraged to 15 | use"; 16 | const HINT: &str = "Try adding an `export {};` to the top of the namespace to disable this behavior"; 17 | 18 | impl LintRule for NoImplicitDeclareNamespaceExport { 19 | fn tags(&self) -> Tags { 20 | &[] 21 | } 22 | 23 | fn code(&self) -> &'static str { 24 | CODE 25 | } 26 | 27 | fn lint_program_with_ast_view( 28 | &self, 29 | context: &mut Context, 30 | program: Program<'_>, 31 | ) { 32 | NoImplicitDeclareNamespaceExportHandler.traverse(program, context); 33 | } 34 | } 35 | 36 | struct NoImplicitDeclareNamespaceExportHandler; 37 | 38 | impl Handler for NoImplicitDeclareNamespaceExportHandler { 39 | fn ts_module_decl( 40 | &mut self, 41 | module_decl: &ast_view::TsModuleDecl, 42 | ctx: &mut Context, 43 | ) { 44 | if module_decl.inner.declare { 45 | if let Some(ast_view::TsNamespaceBody::TsModuleBlock(block)) = 46 | module_decl.body 47 | { 48 | if !block.body.is_empty() { 49 | let has_named_export = block.body.iter().any(|item| { 50 | matches!( 51 | item, 52 | ast_view::ModuleItem::ModuleDecl( 53 | ast_view::ModuleDecl::ExportNamed(_) 54 | ) 55 | ) 56 | }); 57 | let has_non_exported_member = block 58 | .body 59 | .iter() 60 | .any(|item| matches!(item, ast_view::ModuleItem::Stmt(_))); 61 | if !has_named_export && has_non_exported_member { 62 | ctx.add_diagnostic_with_hint( 63 | module_decl.range(), 64 | CODE, 65 | MESSAGE, 66 | HINT, 67 | ); 68 | } 69 | } 70 | } 71 | } 72 | } 73 | } 74 | 75 | #[cfg(test)] 76 | mod tests { 77 | use super::*; 78 | 79 | #[test] 80 | fn use_explicit_namespace_export_valid() { 81 | assert_lint_ok! { 82 | NoImplicitDeclareNamespaceExport, 83 | filename: "file:///foo.ts", 84 | r#" 85 | namespace foo { 86 | type X = 1; 87 | const FOO = 1; 88 | } 89 | "#, 90 | r#"namespace empty {}"#, 91 | r#" 92 | declare namespace foo { 93 | export type X = 1; 94 | } 95 | "#, 96 | r#" 97 | declare namespace bar { 98 | export {}; 99 | type X = 1; 100 | } 101 | "#, 102 | r#" 103 | declare namespace bar { 104 | type X = 1; 105 | type Y = 2; 106 | export { Y }; 107 | } 108 | "#, 109 | r#" 110 | declare namespace empty {} 111 | "#, 112 | }; 113 | 114 | assert_lint_ok! { 115 | NoImplicitDeclareNamespaceExport, 116 | filename: "file:///foo.d.ts", 117 | 118 | r#" 119 | declare namespace foo { 120 | export type X = 1; 121 | } 122 | "#, 123 | r#" 124 | declare namespace bar { 125 | export {}; 126 | type X = 1; 127 | } 128 | "#, 129 | r#" 130 | declare namespace bar { 131 | export {}; 132 | type X = 1; 133 | export type Y = 2; 134 | } 135 | "#, 136 | r#" 137 | declare namespace empty {} 138 | "#, 139 | }; 140 | } 141 | 142 | #[test] 143 | fn use_explicit_namespace_export_invalid() { 144 | assert_lint_err! { 145 | NoImplicitDeclareNamespaceExport, 146 | r#"declare namespace foo { type X = 1; }"#: [ 147 | { 148 | col: 0, 149 | message: MESSAGE, 150 | hint: HINT, 151 | } 152 | ], 153 | }; 154 | 155 | assert_lint_err! { 156 | NoImplicitDeclareNamespaceExport, 157 | r#"declare namespace foo { type X = 1; export type Y = 2; }"#: [ 158 | { col: 0, message: MESSAGE, hint: HINT } 159 | ], 160 | }; 161 | 162 | assert_lint_err! { 163 | NoImplicitDeclareNamespaceExport, 164 | r#"declare namespace foo { interface X {} }"#: [ 165 | { col: 0, message: MESSAGE, hint: HINT } 166 | ], 167 | }; 168 | 169 | assert_lint_err! { 170 | NoImplicitDeclareNamespaceExport, 171 | r#"declare namespace foo { class X {} }"#: [ 172 | { col: 0, message: MESSAGE, hint: HINT } 173 | ], 174 | }; 175 | 176 | assert_lint_err! { 177 | NoImplicitDeclareNamespaceExport, 178 | r#"declare namespace foo { enum X { A } }"#: [ 179 | { col: 0, message: MESSAGE, hint: HINT } 180 | ], 181 | }; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/rules/no_import_assertions.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::swc::parser::token::{IdentLike, KnownIdent, Token, Word}; 8 | use deno_ast::view as ast_view; 9 | use deno_ast::{SourceRanged, SourceRangedForSpanned}; 10 | use if_chain::if_chain; 11 | 12 | #[derive(Debug)] 13 | pub struct NoImportAssertions; 14 | 15 | const CODE: &str = "no-import-assertions"; 16 | const MESSAGE: &str = 17 | "The `assert` keyword is deprecated for import attributes"; 18 | const HINT: &str = "Instead use the `with` keyword"; 19 | 20 | impl LintRule for NoImportAssertions { 21 | fn tags(&self) -> Tags { 22 | &[tags::RECOMMENDED] 23 | } 24 | 25 | fn code(&self) -> &'static str { 26 | CODE 27 | } 28 | 29 | fn lint_program_with_ast_view( 30 | &self, 31 | context: &mut Context, 32 | program: Program<'_>, 33 | ) { 34 | NoImportAssertionsHandler.traverse(program, context); 35 | } 36 | } 37 | 38 | struct NoImportAssertionsHandler; 39 | 40 | impl Handler for NoImportAssertionsHandler { 41 | fn import_decl( 42 | &mut self, 43 | import_decl: &ast_view::ImportDecl, 44 | ctx: &mut Context, 45 | ) { 46 | if_chain! { 47 | if let Some(with) = import_decl.with; 48 | if let Some(prev_token_and_span) = with.start().previous_token_fast(ctx.program()); 49 | if let Token::Word(word) = &prev_token_and_span.token; 50 | if let Word::Ident(ident_like) = word; 51 | if let IdentLike::Known(known_ident) = ident_like; 52 | if matches!(known_ident, KnownIdent::Assert); 53 | then { 54 | ctx.add_diagnostic_with_hint( 55 | prev_token_and_span.span.range(), 56 | CODE, 57 | MESSAGE, 58 | HINT, 59 | ); 60 | } 61 | } 62 | } 63 | 64 | fn call_expr(&mut self, call_expr: &ast_view::CallExpr, ctx: &mut Context) { 65 | if_chain! { 66 | if matches!(call_expr.callee, ast_view::Callee::Import(_)); 67 | if let Some(expr_or_spread) = call_expr.args.get(1); 68 | if let ast_view::Expr::Object(object_lit) = expr_or_spread.expr; 69 | then { 70 | for prop_or_spread in object_lit.props.iter() { 71 | if_chain! { 72 | if let ast_view::PropOrSpread::Prop(prop) = prop_or_spread; 73 | if let ast_view::Prop::KeyValue(key_value_prop) = prop; 74 | then { 75 | match key_value_prop.key { 76 | ast_view::PropName::Ident(ident) => { 77 | if ident.sym().as_ref() == "assert" { 78 | ctx.add_diagnostic_with_hint( 79 | ident.range(), 80 | CODE, 81 | MESSAGE, 82 | HINT, 83 | ); 84 | } 85 | }, 86 | ast_view::PropName::Str(str) => { 87 | if str.value().as_ref() == "assert" { 88 | ctx.add_diagnostic_with_hint( 89 | str.range(), 90 | CODE, 91 | MESSAGE, 92 | HINT, 93 | ); 94 | } 95 | } 96 | _ => (), 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } 105 | 106 | #[cfg(test)] 107 | mod tests { 108 | use super::*; 109 | 110 | #[test] 111 | fn no_import_assertions_valid() { 112 | assert_lint_ok! { 113 | NoImportAssertions, 114 | r#"import foo from './foo.js';"#, 115 | r#"import foo from './foo.js' with { bar: 'bar' };"#, 116 | r#"import('./foo.js');"#, 117 | r#"import('./foo.js', { with: { bar: 'bar' } });"#, 118 | r#"import('./foo.js', { "with": { bar: 'bar' } });"#, 119 | }; 120 | } 121 | 122 | #[test] 123 | fn no_import_assertions_invalid() { 124 | assert_lint_err! { 125 | NoImportAssertions, 126 | MESSAGE, 127 | HINT, 128 | r#"import foo from './foo.js' assert { bar: 'bar' };"#: [ 129 | { 130 | line: 1, 131 | col: 27, 132 | }, 133 | ], 134 | r#"import('./foo.js', { assert: { bar: 'bar' } });"#: [ 135 | { 136 | line: 1, 137 | col: 21, 138 | }, 139 | ], 140 | r#"import('./foo.js', { "assert": { bar: 'bar' } });"#: [ 141 | { 142 | line: 1, 143 | col: 21, 144 | }, 145 | ], 146 | }; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/rules/no_namespace.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::view::NodeTrait; 8 | use deno_ast::{view as ast_view, MediaType, SourceRanged}; 9 | 10 | #[derive(Debug)] 11 | pub struct NoNamespace; 12 | 13 | const CODE: &str = "no-namespace"; 14 | const MESSAGE: &str = "TypeScript's `module` and `namespace` are discouraged to 15 | use"; 16 | const HINT: &str = "Use ES2015 module syntax (`import`/`export`) to organize 17 | the code instead"; 18 | 19 | impl LintRule for NoNamespace { 20 | fn tags(&self) -> Tags { 21 | &[tags::RECOMMENDED] 22 | } 23 | 24 | fn code(&self) -> &'static str { 25 | CODE 26 | } 27 | 28 | fn lint_program_with_ast_view( 29 | &self, 30 | context: &mut Context, 31 | program: Program<'_>, 32 | ) { 33 | if matches!(context.media_type(), MediaType::Dts) { 34 | return; 35 | } 36 | 37 | NoNamespaceHandler.traverse(program, context); 38 | } 39 | } 40 | 41 | struct NoNamespaceHandler; 42 | 43 | impl Handler for NoNamespaceHandler { 44 | fn ts_module_decl( 45 | &mut self, 46 | module_decl: &ast_view::TsModuleDecl, 47 | ctx: &mut Context, 48 | ) { 49 | fn inside_ambient_context(current_node: ast_view::Node) -> bool { 50 | use deno_ast::view::Node::*; 51 | match current_node { 52 | TsModuleDecl(module_decl) if module_decl.declare() => true, 53 | _ => match current_node.parent() { 54 | Some(p) => inside_ambient_context(p), 55 | None => false, 56 | }, 57 | } 58 | } 59 | 60 | if !inside_ambient_context(module_decl.as_node()) { 61 | ctx.add_diagnostic_with_hint(module_decl.range(), CODE, MESSAGE, HINT); 62 | } 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | 70 | #[test] 71 | fn no_namespace_valid() { 72 | assert_lint_ok! { 73 | NoNamespace, 74 | filename: "file:///foo.ts", 75 | 76 | r#"declare global {}"#, 77 | r#"declare module 'foo' {}"#, 78 | r#"declare module foo {}"#, 79 | r#"declare namespace foo {}"#, 80 | r#" 81 | declare global { 82 | namespace foo {} 83 | } 84 | "#, 85 | r#" 86 | declare module foo { 87 | namespace bar {} 88 | } 89 | "#, 90 | r#" 91 | declare global { 92 | namespace foo { 93 | namespace bar {} 94 | } 95 | } 96 | "#, 97 | r#" 98 | declare namespace foo { 99 | namespace bar { 100 | namespace baz {} 101 | } 102 | } 103 | "#, 104 | }; 105 | 106 | assert_lint_ok! { 107 | NoNamespace, 108 | filename: "file:///test.d.ts", 109 | 110 | r#"namespace foo {}"#, 111 | r#"module foo {}"#, 112 | 113 | // https://github.com/denoland/deno_lint/issues/633 114 | r#" 115 | export declare namespace Utility { 116 | export namespace Matcher { 117 | export type CharSchema< 118 | T extends string, 119 | Schema extends string, 120 | _Rest extends string = T 121 | > = _Rest extends `${infer $First}${infer $Rest}` 122 | ? $First extends Schema 123 | ? CharSchema 124 | : never 125 | : "" extends _Rest 126 | ? T 127 | : never; 128 | } 129 | } 130 | "#, 131 | }; 132 | } 133 | 134 | #[test] 135 | fn no_namespace_invalid() { 136 | assert_lint_err! { 137 | NoNamespace, 138 | "module foo {}": [ 139 | { 140 | col: 0, 141 | message: MESSAGE, 142 | hint: HINT, 143 | }, 144 | ], 145 | "namespace foo {}": [ 146 | { 147 | col: 0, 148 | message: MESSAGE, 149 | hint: HINT, 150 | } 151 | ], 152 | "namespace Foo.Bar {}": [ 153 | { 154 | col: 0, 155 | message: MESSAGE, 156 | hint: HINT, 157 | } 158 | ], 159 | "namespace Foo.Bar { namespace Baz.Bas {} }": [ 160 | { 161 | col: 0, 162 | message: MESSAGE, 163 | hint: HINT, 164 | }, 165 | { 166 | col: 20, 167 | message: MESSAGE, 168 | hint: HINT, 169 | }, 170 | ], 171 | }; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/rules/no_new_symbol.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::view::{Expr, NewExpr}; 8 | use deno_ast::SourceRanged; 9 | use if_chain::if_chain; 10 | 11 | #[derive(Debug)] 12 | pub struct NoNewSymbol; 13 | 14 | const CODE: &str = "no-new-symbol"; 15 | const MESSAGE: &str = "`Symbol` cannot be called as a constructor."; 16 | 17 | impl LintRule for NoNewSymbol { 18 | fn tags(&self) -> Tags { 19 | &[tags::RECOMMENDED] 20 | } 21 | 22 | fn code(&self) -> &'static str { 23 | CODE 24 | } 25 | 26 | fn lint_program_with_ast_view( 27 | &self, 28 | context: &mut Context, 29 | program: Program, 30 | ) { 31 | NoNewSymbolHandler.traverse(program, context); 32 | } 33 | } 34 | 35 | struct NoNewSymbolHandler; 36 | 37 | impl Handler for NoNewSymbolHandler { 38 | fn new_expr(&mut self, new_expr: &NewExpr, ctx: &mut Context) { 39 | if_chain! { 40 | if let Expr::Ident(ident) = new_expr.callee; 41 | if *ident.sym() == *"Symbol"; 42 | if ctx.scope().var(&ident.to_id()).is_none(); 43 | then { 44 | ctx.add_diagnostic(new_expr.range(), CODE, MESSAGE); 45 | } 46 | } 47 | } 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::*; 53 | 54 | #[test] 55 | fn no_new_symbol_valid() { 56 | assert_lint_ok! { 57 | NoNewSymbol, 58 | "new Class()", 59 | "Symbol()", 60 | // not a built-in Symbol 61 | r#" 62 | function f(Symbol: typeof SomeClass) { 63 | const foo = new Symbol(); 64 | } 65 | "#, 66 | }; 67 | } 68 | 69 | #[test] 70 | fn no_new_symbol_invalid() { 71 | assert_lint_err! { 72 | NoNewSymbol, 73 | "new Symbol()": [{ col: 0, message: MESSAGE }], 74 | // nested 75 | "new class { foo() { new Symbol(); } }": [{ col: 20, message: MESSAGE }], 76 | }; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/rules/no_non_null_asserted_optional_chain.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::Program; 6 | use deno_ast::view::{Callee, Expr, TsNonNullExpr}; 7 | use deno_ast::{SourceRange, SourceRanged}; 8 | use derive_more::Display; 9 | 10 | #[derive(Debug)] 11 | pub struct NoNonNullAssertedOptionalChain; 12 | 13 | const CODE: &str = "no-non-null-asserted-optional-chain"; 14 | 15 | #[derive(Display)] 16 | enum NoNonNullAssertedOptionalChainMessage { 17 | #[display( 18 | fmt = "Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong." 19 | )] 20 | WrongAssertion, 21 | } 22 | 23 | impl LintRule for NoNonNullAssertedOptionalChain { 24 | fn code(&self) -> &'static str { 25 | CODE 26 | } 27 | 28 | fn lint_program_with_ast_view( 29 | &self, 30 | context: &mut Context, 31 | program: Program, 32 | ) { 33 | NoNonNullAssertedOptionalChainHandler.traverse(program, context); 34 | } 35 | } 36 | 37 | struct NoNonNullAssertedOptionalChainHandler; 38 | 39 | fn check_expr_for_nested_optional_assert( 40 | range: SourceRange, 41 | expr: &Expr, 42 | ctx: &mut Context, 43 | ) { 44 | if let Expr::OptChain(_) = expr { 45 | ctx.add_diagnostic( 46 | range, 47 | CODE, 48 | NoNonNullAssertedOptionalChainMessage::WrongAssertion, 49 | ); 50 | } 51 | } 52 | 53 | impl Handler for NoNonNullAssertedOptionalChainHandler { 54 | fn ts_non_null_expr( 55 | &mut self, 56 | ts_non_null_expr: &TsNonNullExpr, 57 | ctx: &mut Context, 58 | ) { 59 | match ts_non_null_expr.expr { 60 | Expr::Member(member_expr) => { 61 | check_expr_for_nested_optional_assert( 62 | ts_non_null_expr.range(), 63 | &member_expr.obj, 64 | ctx, 65 | ); 66 | } 67 | Expr::Call(call_expr) => { 68 | if let Callee::Expr(expr) = &call_expr.callee { 69 | check_expr_for_nested_optional_assert( 70 | ts_non_null_expr.range(), 71 | expr, 72 | ctx, 73 | ); 74 | } 75 | } 76 | Expr::Paren(paren_expr) => check_expr_for_nested_optional_assert( 77 | ts_non_null_expr.range(), 78 | &paren_expr.expr, 79 | ctx, 80 | ), 81 | _ => {} 82 | }; 83 | 84 | check_expr_for_nested_optional_assert( 85 | ts_non_null_expr.range(), 86 | &ts_non_null_expr.expr, 87 | ctx, 88 | ); 89 | } 90 | } 91 | 92 | #[cfg(test)] 93 | mod tests { 94 | use super::*; 95 | 96 | #[test] 97 | fn no_non_null_asserted_optional_chain_valid() { 98 | assert_lint_ok! { 99 | NoNonNullAssertedOptionalChain, 100 | "foo.bar!;", 101 | "foo.bar()!;", 102 | "foo?.bar();", 103 | "foo?.bar;", 104 | "(foo?.bar).baz!;", 105 | "(foo?.bar()).baz!;", 106 | }; 107 | } 108 | 109 | #[test] 110 | fn no_non_null_asserted_optional_chain_invalid() { 111 | assert_lint_err! { 112 | NoNonNullAssertedOptionalChain, 113 | r#"foo?.bar!;"#: [ 114 | { 115 | col: 0, 116 | message: NoNonNullAssertedOptionalChainMessage::WrongAssertion, 117 | }], 118 | r#"foo?.['bar']!;"#: [ 119 | { 120 | col: 0, 121 | message: NoNonNullAssertedOptionalChainMessage::WrongAssertion, 122 | }], 123 | r#"foo?.bar()!;"#: [ 124 | { 125 | col: 0, 126 | message: NoNonNullAssertedOptionalChainMessage::WrongAssertion, 127 | }], 128 | r#"foo.bar?.()!;"#: [ 129 | { 130 | col: 0, 131 | message: NoNonNullAssertedOptionalChainMessage::WrongAssertion, 132 | }], 133 | r#"(foo?.bar)!.baz"#: [ 134 | { 135 | col: 0, 136 | message: NoNonNullAssertedOptionalChainMessage::WrongAssertion, 137 | }], 138 | r#"(foo?.bar)!().baz"#: [ 139 | { 140 | col: 0, 141 | message: NoNonNullAssertedOptionalChainMessage::WrongAssertion, 142 | }], 143 | r#"(foo?.bar)!"#: [ 144 | { 145 | col: 0, 146 | message: NoNonNullAssertedOptionalChainMessage::WrongAssertion, 147 | }], 148 | r#"(foo?.bar)!()"#: [ 149 | { 150 | col: 0, 151 | message: NoNonNullAssertedOptionalChainMessage::WrongAssertion, 152 | }], 153 | r#"(foo?.bar!)"#: [ 154 | { 155 | col: 1, 156 | message: NoNonNullAssertedOptionalChainMessage::WrongAssertion, 157 | }], 158 | r#"(foo?.bar!)()"#: [ 159 | { 160 | col: 1, 161 | message: NoNonNullAssertedOptionalChainMessage::WrongAssertion, 162 | }], 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/rules/no_non_null_assertion.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::Program; 6 | use deno_ast::view::TsNonNullExpr; 7 | use deno_ast::SourceRanged; 8 | use derive_more::Display; 9 | 10 | #[derive(Debug)] 11 | pub struct NoNonNullAssertion; 12 | 13 | const CODE: &str = "no-non-null-assertion"; 14 | 15 | #[derive(Display)] 16 | enum NoNonNullAssertionMessage { 17 | #[display(fmt = "do not use non-null assertion")] 18 | Unexpected, 19 | } 20 | 21 | impl LintRule for NoNonNullAssertion { 22 | fn code(&self) -> &'static str { 23 | CODE 24 | } 25 | 26 | fn lint_program_with_ast_view( 27 | &self, 28 | context: &mut Context, 29 | program: Program, 30 | ) { 31 | NoNonNullAssertionHandler.traverse(program, context); 32 | } 33 | } 34 | 35 | struct NoNonNullAssertionHandler; 36 | 37 | impl Handler for NoNonNullAssertionHandler { 38 | fn ts_non_null_expr( 39 | &mut self, 40 | non_null_expr: &TsNonNullExpr, 41 | ctx: &mut Context, 42 | ) { 43 | if !non_null_expr.parent().is::() { 44 | ctx.add_diagnostic( 45 | non_null_expr.range(), 46 | CODE, 47 | NoNonNullAssertionMessage::Unexpected, 48 | ); 49 | } 50 | } 51 | } 52 | 53 | #[cfg(test)] 54 | mod tests { 55 | use super::*; 56 | 57 | #[test] 58 | fn no_non_null_assertion_valid() { 59 | assert_lint_ok! { 60 | NoNonNullAssertion, 61 | "instance.doWork();", 62 | "foo.bar?.includes('baz')", 63 | "x;", 64 | "x.y;", 65 | "x.y.z;", 66 | "x?.y.z;", 67 | "x?.y?.z;", 68 | "!x;", 69 | }; 70 | } 71 | 72 | #[test] 73 | fn no_non_null_assertion_invalid() { 74 | assert_lint_err! { 75 | NoNonNullAssertion, 76 | 77 | r#"instance!.doWork()"#: [ 78 | { 79 | col: 0, 80 | message: NoNonNullAssertionMessage::Unexpected, 81 | }], 82 | r#"foo.bar!.includes('baz');"#: [ 83 | { 84 | col: 0, 85 | message: NoNonNullAssertionMessage::Unexpected, 86 | }], 87 | r#"x.y.z!?.();"#: [ 88 | { 89 | col: 0, 90 | message: NoNonNullAssertionMessage::Unexpected, 91 | }], 92 | r#"x!?.y.z;"#: [ 93 | { 94 | col: 0, 95 | message: NoNonNullAssertionMessage::Unexpected, 96 | }], 97 | r#"x!?.[y].z;"#: [ 98 | { 99 | col: 0, 100 | message: NoNonNullAssertionMessage::Unexpected, 101 | }], 102 | r#"x.y.z!!();"#: [ 103 | { 104 | col: 0, 105 | message: NoNonNullAssertionMessage::Unexpected, 106 | }], 107 | r#"x.y!!;"#: [ 108 | { 109 | col: 0, 110 | message: NoNonNullAssertionMessage::Unexpected, 111 | }], 112 | r#"x!!.y;"#: [ 113 | { 114 | col: 0, 115 | message: NoNonNullAssertionMessage::Unexpected, 116 | }], 117 | r#"x!!!;"#: [ 118 | { 119 | col: 0, 120 | message: NoNonNullAssertionMessage::Unexpected, 121 | }], 122 | r#"x.y?.z!();"#: [ 123 | { 124 | col: 0, 125 | message: NoNonNullAssertionMessage::Unexpected, 126 | }], 127 | r#"x.y.z!();"#: [ 128 | { 129 | col: 0, 130 | message: NoNonNullAssertionMessage::Unexpected, 131 | }], 132 | r#"x![y]?.z;"#: [ 133 | { 134 | col: 0, 135 | message: NoNonNullAssertionMessage::Unexpected, 136 | }], 137 | r#"x![y];"#: [ 138 | { 139 | col: 0, 140 | message: NoNonNullAssertionMessage::Unexpected, 141 | }], 142 | r#"!x!.y;"#: [ 143 | { 144 | col: 1, 145 | message: NoNonNullAssertionMessage::Unexpected, 146 | }], 147 | r#"x!.y?.z;"#: [ 148 | { 149 | col: 0, 150 | message: NoNonNullAssertionMessage::Unexpected, 151 | }], 152 | r#"x.y!;"#: [ 153 | { 154 | col: 0, 155 | message: NoNonNullAssertionMessage::Unexpected, 156 | }], 157 | r#"x!.y;"#: [ 158 | { 159 | col: 0, 160 | message: NoNonNullAssertionMessage::Unexpected, 161 | }], 162 | r#"x!;"#: [ 163 | { 164 | col: 0, 165 | message: NoNonNullAssertionMessage::Unexpected, 166 | }], 167 | 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/rules/no_obj_calls.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::view::{CallExpr, Callee, Expr, Ident, NewExpr}; 8 | use deno_ast::{SourceRange, SourceRanged}; 9 | 10 | #[derive(Debug)] 11 | pub struct NoObjCalls; 12 | 13 | const CODE: &str = "no-obj-calls"; 14 | 15 | fn get_message(callee_name: &str) -> String { 16 | format!("`{}` call as function is not allowed", callee_name) 17 | } 18 | 19 | impl LintRule for NoObjCalls { 20 | fn tags(&self) -> Tags { 21 | &[tags::RECOMMENDED] 22 | } 23 | 24 | fn code(&self) -> &'static str { 25 | CODE 26 | } 27 | 28 | fn lint_program_with_ast_view( 29 | &self, 30 | context: &mut Context, 31 | program: Program, 32 | ) { 33 | NoObjCallsHandler.traverse(program, context); 34 | } 35 | } 36 | 37 | struct NoObjCallsHandler; 38 | 39 | fn check_callee(callee: &Ident, range: SourceRange, ctx: &mut Context) { 40 | if matches!( 41 | callee.sym().as_ref(), 42 | "Math" | "JSON" | "Reflect" | "Atomics" 43 | ) && ctx.scope().var(&callee.to_id()).is_none() 44 | { 45 | ctx.add_diagnostic( 46 | range, 47 | "no-obj-calls", 48 | get_message(callee.sym().as_ref()), 49 | ); 50 | } 51 | } 52 | 53 | impl Handler for NoObjCallsHandler { 54 | fn call_expr(&mut self, call_expr: &CallExpr, ctx: &mut Context) { 55 | if let Callee::Expr(Expr::Ident(ident)) = call_expr.callee { 56 | check_callee(ident, call_expr.range(), ctx); 57 | } 58 | } 59 | 60 | fn new_expr(&mut self, new_expr: &NewExpr, ctx: &mut Context) { 61 | if let Expr::Ident(ident) = new_expr.callee { 62 | check_callee(ident, new_expr.range(), ctx); 63 | } 64 | } 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use super::*; 70 | 71 | #[test] 72 | fn no_obj_calls_valid() { 73 | assert_lint_ok! { 74 | NoObjCalls, 75 | "Math.PI * 2 * 3;", 76 | r#"JSON.parse("{}");"#, 77 | r#"Reflect.get({ x: 1, y: 2 }, "x");"#, 78 | "Atomics.load(foo, 0);", 79 | r#" 80 | function f(Math: () => void) { 81 | Math(); 82 | } 83 | "#, 84 | r#" 85 | function f(JSON: () => void) { 86 | JSON(); 87 | } 88 | "#, 89 | r#" 90 | function f(Reflect: () => void) { 91 | Reflect(); 92 | } 93 | "#, 94 | r#" 95 | function f(Atomics: () => void) { 96 | Atomics(); 97 | } 98 | "#, 99 | }; 100 | } 101 | 102 | #[test] 103 | fn no_obj_calls_invalid() { 104 | assert_lint_err! { 105 | NoObjCalls, 106 | "Math();": [{col: 0, message: get_message("Math")}], 107 | "new Math();": [{col: 0, message: get_message("Math")}], 108 | "JSON();": [{col: 0, message: get_message("JSON")}], 109 | "new JSON();": [{col: 0, message: get_message("JSON")}], 110 | "Reflect();": [{col: 0, message: get_message("Reflect")}], 111 | "new Reflect();": [{col: 0, message: get_message("Reflect")}], 112 | "Atomics();": [{col: 0, message: get_message("Atomics")}], 113 | "new Atomics();": [{col: 0, message: get_message("Atomics")}], 114 | r#" 115 | function f(Math: () => void) { Math(); } 116 | const m = Math(); 117 | "#: [ 118 | { 119 | col: 10, 120 | line: 3, 121 | message: get_message("Math"), 122 | }, 123 | ], 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/rules/no_octal.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::view::Number; 8 | use deno_ast::SourceRanged; 9 | use once_cell::sync::Lazy; 10 | use regex::Regex; 11 | 12 | #[derive(Debug)] 13 | pub struct NoOctal; 14 | 15 | const CODE: &str = "no-octal"; 16 | const MESSAGE: &str = "Numeric literals beginning with `0` are not allowed"; 17 | const HINT: &str = "To express octal numbers, use `0o` as a prefix instead"; 18 | 19 | impl LintRule for NoOctal { 20 | fn tags(&self) -> Tags { 21 | &[tags::RECOMMENDED] 22 | } 23 | 24 | fn code(&self) -> &'static str { 25 | CODE 26 | } 27 | 28 | fn lint_program_with_ast_view( 29 | &self, 30 | context: &mut Context, 31 | program: Program, 32 | ) { 33 | NoOctalHandler.traverse(program, context); 34 | } 35 | } 36 | 37 | struct NoOctalHandler; 38 | 39 | impl Handler for NoOctalHandler { 40 | fn number(&mut self, literal_num: &Number, ctx: &mut Context) { 41 | static OCTAL: Lazy = Lazy::new(|| Regex::new(r"^0[0-9]").unwrap()); 42 | 43 | let raw_number = literal_num.text_fast(ctx.text_info()); 44 | 45 | if OCTAL.is_match(raw_number) { 46 | ctx.add_diagnostic_with_hint(literal_num.range(), CODE, MESSAGE, HINT); 47 | } 48 | } 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | 55 | #[test] 56 | fn no_octal_valid() { 57 | assert_lint_ok! { 58 | NoOctal, 59 | "7", 60 | "\"07\"", 61 | "0x08", 62 | "-0.01", 63 | }; 64 | } 65 | 66 | #[test] 67 | fn no_octal_invalid() { 68 | assert_lint_err! { 69 | NoOctal, 70 | "07": [{col: 0, message: MESSAGE, hint: HINT}], 71 | "let x = 7 + 07": [{col: 12, message: MESSAGE, hint: HINT}], 72 | 73 | // https://github.com/denoland/deno/issues/10954 74 | // Make sure it doesn't panic 75 | "020000000000000000000;": [{col: 0, message: MESSAGE, hint: HINT}], 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/rules/no_prototype_builtins.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::view::{CallExpr, Callee, Expr, MemberProp}; 8 | use deno_ast::SourceRanged; 9 | 10 | const BANNED_PROPERTIES: &[&str] = 11 | &["hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable"]; 12 | 13 | #[derive(Debug)] 14 | pub struct NoPrototypeBuiltins; 15 | 16 | const CODE: &str = "no-prototype-builtins"; 17 | 18 | fn get_message(prop: &str) -> String { 19 | format!( 20 | "Access to Object.prototype.{} is not allowed from target object", 21 | prop 22 | ) 23 | } 24 | 25 | impl LintRule for NoPrototypeBuiltins { 26 | fn tags(&self) -> Tags { 27 | &[tags::RECOMMENDED] 28 | } 29 | 30 | fn code(&self) -> &'static str { 31 | CODE 32 | } 33 | 34 | fn lint_program_with_ast_view( 35 | &self, 36 | context: &mut Context, 37 | program: Program, 38 | ) { 39 | NoPrototypeBuiltinsHandler.traverse(program, context); 40 | } 41 | } 42 | 43 | struct NoPrototypeBuiltinsHandler; 44 | 45 | impl Handler for NoPrototypeBuiltinsHandler { 46 | fn call_expr(&mut self, call_expr: &CallExpr, ctx: &mut Context) { 47 | let member_expr = match call_expr.callee { 48 | Callee::Expr(boxed_expr) => match boxed_expr { 49 | Expr::Member(member_expr) => member_expr, 50 | _ => return, 51 | }, 52 | Callee::Super(_) | Callee::Import(_) => return, 53 | }; 54 | 55 | if let MemberProp::Ident(ident) = member_expr.prop { 56 | let prop_name = ident.sym().as_ref(); 57 | if BANNED_PROPERTIES.contains(&prop_name) { 58 | ctx.add_diagnostic(call_expr.range(), CODE, get_message(prop_name)); 59 | } 60 | } 61 | } 62 | } 63 | 64 | #[cfg(test)] 65 | mod tests { 66 | use super::*; 67 | 68 | #[test] 69 | fn no_prototype_builtins_valid() { 70 | assert_lint_ok! { 71 | NoPrototypeBuiltins, 72 | r#" 73 | Object.prototype.hasOwnProperty.call(foo, "bar"); 74 | Object.prototype.isPrototypeOf.call(foo, "bar"); 75 | Object.prototype.propertyIsEnumerable.call(foo, "bar"); 76 | Object.prototype.hasOwnProperty.apply(foo, ["bar"]); 77 | Object.prototype.isPrototypeOf.apply(foo, ["bar"]); 78 | Object.prototype.propertyIsEnumerable.apply(foo, ["bar"]); 79 | hasOwnProperty(foo, "bar"); 80 | isPrototypeOf(foo, "bar"); 81 | propertyIsEnumerable(foo, "bar"); 82 | ({}.hasOwnProperty.call(foo, "bar")); 83 | ({}.isPrototypeOf.call(foo, "bar")); 84 | ({}.propertyIsEnumerable.call(foo, "bar")); 85 | ({}.hasOwnProperty.apply(foo, ["bar"])); 86 | ({}.isPrototypeOf.apply(foo, ["bar"])); 87 | ({}.propertyIsEnumerable.apply(foo, ["bar"])); 88 | "#, 89 | }; 90 | } 91 | 92 | #[test] 93 | fn no_prototype_builtins_invalid() { 94 | assert_lint_err! { 95 | NoPrototypeBuiltins, 96 | "foo.hasOwnProperty('bar');": [{col: 0, message: get_message("hasOwnProperty")}], 97 | "foo.isPrototypeOf('bar');": [{col: 0, message: get_message("isPrototypeOf")}], 98 | "foo.propertyIsEnumerable('bar');": [{col: 0, message: get_message("propertyIsEnumerable")}], 99 | "foo.bar.baz.hasOwnProperty('bar');": [{col: 0, message: get_message("hasOwnProperty")}], 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/rules/no_sparse_arrays.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::Program; 6 | use deno_ast::view::ArrayLit; 7 | use deno_ast::SourceRanged; 8 | use derive_more::Display; 9 | 10 | #[derive(Debug)] 11 | pub struct NoSparseArrays; 12 | 13 | const CODE: &str = "no-sparse-arrays"; 14 | 15 | #[derive(Display)] 16 | enum NoSparseArraysMessage { 17 | #[display(fmt = "Sparse arrays are not allowed")] 18 | Disallowed, 19 | } 20 | 21 | impl LintRule for NoSparseArrays { 22 | fn code(&self) -> &'static str { 23 | CODE 24 | } 25 | 26 | fn lint_program_with_ast_view( 27 | &self, 28 | context: &mut Context, 29 | program: Program, 30 | ) { 31 | NoSparseArraysHandler.traverse(program, context); 32 | } 33 | } 34 | 35 | struct NoSparseArraysHandler; 36 | 37 | impl Handler for NoSparseArraysHandler { 38 | fn array_lit(&mut self, array_lit: &ArrayLit, ctx: &mut Context) { 39 | if array_lit.elems.iter().any(|e| e.is_none()) { 40 | ctx.add_diagnostic( 41 | array_lit.range(), 42 | CODE, 43 | NoSparseArraysMessage::Disallowed, 44 | ); 45 | } 46 | } 47 | } 48 | 49 | #[cfg(test)] 50 | mod tests { 51 | use super::*; 52 | 53 | #[test] 54 | fn no_sparse_arrays_valid() { 55 | assert_lint_ok! { 56 | NoSparseArrays, 57 | "const sparseArray1 = [1,null,3];", 58 | }; 59 | } 60 | 61 | #[test] 62 | fn no_sparse_arrays_invalid() { 63 | assert_lint_err! { 64 | NoSparseArrays, 65 | r#"const sparseArray = [1,,3];"#: [ 66 | { 67 | col: 20, 68 | message: NoSparseArraysMessage::Disallowed, 69 | }], 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/rules/no_this_alias.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::view::{Expr, Pat, VarDecl}; 8 | use deno_ast::SourceRanged; 9 | use if_chain::if_chain; 10 | 11 | #[derive(Debug)] 12 | pub struct NoThisAlias; 13 | 14 | const CODE: &str = "no-this-alias"; 15 | const MESSAGE: &str = "assign `this` to declare a value is not allowed"; 16 | 17 | impl LintRule for NoThisAlias { 18 | fn tags(&self) -> Tags { 19 | &[tags::RECOMMENDED] 20 | } 21 | 22 | fn code(&self) -> &'static str { 23 | CODE 24 | } 25 | 26 | fn lint_program_with_ast_view( 27 | &self, 28 | context: &mut Context, 29 | program: Program, 30 | ) { 31 | NoThisAliasHandler.traverse(program, context); 32 | } 33 | } 34 | 35 | struct NoThisAliasHandler; 36 | 37 | impl Handler for NoThisAliasHandler { 38 | fn var_decl(&mut self, var_decl: &VarDecl, ctx: &mut Context) { 39 | for decl in var_decl.decls { 40 | if_chain! { 41 | if let Some(init) = &decl.init; 42 | if matches!(&init, Expr::This(_)); 43 | if matches!(&decl.name, Pat::Ident(_)); 44 | then { 45 | ctx.add_diagnostic(var_decl.range(), CODE, MESSAGE); 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | use super::*; 55 | 56 | #[test] 57 | fn no_this_alias_valid() { 58 | assert_lint_ok! { 59 | NoThisAlias, 60 | "const self = foo(this);", 61 | "const self = 'this';", 62 | "const { props, state } = this;", 63 | "const [foo] = this;", 64 | }; 65 | } 66 | 67 | #[test] 68 | fn no_this_alias_invalid() { 69 | assert_lint_err! { 70 | NoThisAlias, 71 | "const self = this;": [ 72 | { 73 | col: 0, 74 | message: MESSAGE, 75 | } 76 | ], 77 | " 78 | var unscoped = this; 79 | 80 | function testFunction() { 81 | let inFunction = this; 82 | } 83 | 84 | const testLambda = () => { 85 | const inLambda = this; 86 | };": [ 87 | { 88 | line: 2, 89 | col: 0, 90 | message: MESSAGE, 91 | }, 92 | { 93 | line: 5, 94 | col: 2, 95 | message: MESSAGE, 96 | }, 97 | { 98 | line: 9, 99 | col: 2, 100 | message: MESSAGE, 101 | } 102 | ], 103 | " 104 | class TestClass { 105 | constructor() { 106 | const inConstructor = this; 107 | const asThis: this = this; 108 | 109 | const asString = 'this'; 110 | const asArray = [this]; 111 | const asArrayString = ['this']; 112 | } 113 | 114 | public act(scope: this = this) { 115 | const inMemberFunction = this; 116 | } 117 | }": [ 118 | { 119 | line: 4, 120 | col: 4, 121 | message: MESSAGE, 122 | }, 123 | { 124 | line: 5, 125 | col: 4, 126 | message: MESSAGE, 127 | }, 128 | { 129 | line: 13, 130 | col: 4, 131 | message: MESSAGE, 132 | } 133 | ], 134 | "const foo = function() { const self = this; };": [ 135 | { 136 | col: 25, 137 | message: MESSAGE, 138 | } 139 | ] 140 | }; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/rules/no_throw_literal.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::Program; 6 | use deno_ast::view::{Expr, ThrowStmt}; 7 | use deno_ast::SourceRanged; 8 | use derive_more::Display; 9 | 10 | #[derive(Debug)] 11 | pub struct NoThrowLiteral; 12 | 13 | const CODE: &str = "no-throw-literal"; 14 | 15 | #[derive(Display)] 16 | enum NoThrowLiteralMessage { 17 | #[display(fmt = "expected an error object to be thrown")] 18 | ErrObjectExpected, 19 | 20 | #[display(fmt = "do not throw undefined")] 21 | Undefined, 22 | } 23 | 24 | impl LintRule for NoThrowLiteral { 25 | fn code(&self) -> &'static str { 26 | CODE 27 | } 28 | 29 | fn lint_program_with_ast_view( 30 | &self, 31 | context: &mut Context, 32 | program: Program, 33 | ) { 34 | NoThrowLiteralHandler.traverse(program, context); 35 | } 36 | } 37 | 38 | struct NoThrowLiteralHandler; 39 | 40 | impl Handler for NoThrowLiteralHandler { 41 | fn throw_stmt(&mut self, throw_stmt: &ThrowStmt, ctx: &mut Context) { 42 | match throw_stmt.arg { 43 | Expr::Lit(_) => ctx.add_diagnostic( 44 | throw_stmt.range(), 45 | CODE, 46 | NoThrowLiteralMessage::ErrObjectExpected, 47 | ), 48 | Expr::Ident(ident) if *ident.sym() == *"undefined" => ctx.add_diagnostic( 49 | throw_stmt.range(), 50 | CODE, 51 | NoThrowLiteralMessage::Undefined, 52 | ), 53 | _ => {} 54 | } 55 | } 56 | } 57 | 58 | #[cfg(test)] 59 | mod tests { 60 | use super::*; 61 | 62 | #[test] 63 | fn no_throw_literal_valid() { 64 | assert_lint_ok! { 65 | NoThrowLiteral, 66 | "throw e", 67 | }; 68 | } 69 | 70 | #[test] 71 | fn no_throw_literal_invalid() { 72 | assert_lint_err! { 73 | NoThrowLiteral, 74 | r#"throw 'kumiko'"#: [ 75 | { 76 | col: 0, 77 | message: NoThrowLiteralMessage::ErrObjectExpected, 78 | }], 79 | r#"throw true"#: [ 80 | { 81 | col: 0, 82 | message: NoThrowLiteralMessage::ErrObjectExpected, 83 | }], 84 | r#"throw 1096"#: [ 85 | { 86 | col: 0, 87 | message: NoThrowLiteralMessage::ErrObjectExpected, 88 | }], 89 | r#"throw null"#: [ 90 | { 91 | col: 0, 92 | message: NoThrowLiteralMessage::ErrObjectExpected, 93 | }], 94 | r#"throw undefined"#: [ 95 | { 96 | col: 0, 97 | message: NoThrowLiteralMessage::Undefined, 98 | }], 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/rules/no_top_level_await.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::Program; 6 | use deno_ast::view::NodeTrait; 7 | use deno_ast::view::{self as ast_view}; 8 | use deno_ast::SourceRanged; 9 | use if_chain::if_chain; 10 | 11 | #[derive(Debug)] 12 | pub struct NoTopLevelAwait; 13 | 14 | const CODE: &str = "no-top-level-await"; 15 | const MESSAGE: &str = "Top level await is not allowed"; 16 | 17 | impl LintRule for NoTopLevelAwait { 18 | fn code(&self) -> &'static str { 19 | CODE 20 | } 21 | 22 | fn lint_program_with_ast_view( 23 | &self, 24 | context: &mut Context, 25 | program: Program<'_>, 26 | ) { 27 | NoTopLevelAwaitHandler.traverse(program, context); 28 | } 29 | } 30 | 31 | struct NoTopLevelAwaitHandler; 32 | 33 | impl Handler for NoTopLevelAwaitHandler { 34 | fn await_expr( 35 | &mut self, 36 | await_expr: &ast_view::AwaitExpr, 37 | ctx: &mut Context, 38 | ) { 39 | if !is_node_inside_function(await_expr) { 40 | ctx.add_diagnostic(await_expr.range(), CODE, MESSAGE); 41 | } 42 | } 43 | 44 | fn for_of_stmt( 45 | &mut self, 46 | for_of_stmt: &ast_view::ForOfStmt, 47 | ctx: &mut Context, 48 | ) { 49 | if_chain! { 50 | if for_of_stmt.is_await(); 51 | if !is_node_inside_function(for_of_stmt); 52 | then { 53 | ctx.add_diagnostic(for_of_stmt.range(), CODE, MESSAGE) 54 | } 55 | } 56 | } 57 | } 58 | 59 | fn is_node_inside_function<'a>(node: &impl NodeTrait<'a>) -> bool { 60 | use deno_ast::view::Node; 61 | match node.parent() { 62 | Some(Node::FnDecl(_)) 63 | | Some(Node::FnExpr(_)) 64 | | Some(Node::ArrowExpr(_)) 65 | | Some(Node::ClassMethod(_)) 66 | | Some(Node::PrivateMethod(_)) => true, 67 | None => false, 68 | Some(n) => is_node_inside_function(&n), 69 | } 70 | } 71 | 72 | #[cfg(test)] 73 | mod tests { 74 | use super::*; 75 | 76 | #[test] 77 | fn no_top_level_await_valid() { 78 | assert_lint_ok! { 79 | NoTopLevelAwait, 80 | r#"async function foo() { await bar(); }"#, 81 | r#"const foo = async function () { await bar()};"#, 82 | r#"const foo = () => { await bar()};"#, 83 | r#"async function foo() { for await (item of items){}}"#, 84 | r#"async function foo() { await bar(); }"#, 85 | r#"class Foo { 86 | async foo() { await task(); } 87 | private async bar(){ await task(); } 88 | }"#, 89 | r#"const foo = { bar : async () => { await task()} }"#, 90 | }; 91 | } 92 | 93 | #[test] 94 | fn no_top_level_await_invalid() { 95 | assert_lint_err! { 96 | NoTopLevelAwait, 97 | r#"await foo()"#: [ 98 | { 99 | col: 0, 100 | message: MESSAGE, 101 | }, 102 | ], 103 | r#"for await (item of items) {}"#: [ 104 | { 105 | col: 0, 106 | message: MESSAGE, 107 | }, 108 | ], 109 | }; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/rules/no_unsafe_negation.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::{view as ast_view, SourceRanged}; 8 | use derive_more::Display; 9 | use if_chain::if_chain; 10 | 11 | #[derive(Debug)] 12 | pub struct NoUnsafeNegation; 13 | 14 | const CODE: &str = "no-unsafe-negation"; 15 | 16 | #[derive(Display)] 17 | enum NoUnsafeNegationMessage { 18 | #[display(fmt = "Unexpected negating the left operand of `{}` operator", _0)] 19 | Unexpected(String), 20 | } 21 | 22 | const HINT: &str = "Add parentheses to clarify which range the negation operator should be applied to"; 23 | 24 | impl LintRule for NoUnsafeNegation { 25 | fn tags(&self) -> Tags { 26 | &[tags::RECOMMENDED] 27 | } 28 | 29 | fn code(&self) -> &'static str { 30 | CODE 31 | } 32 | 33 | fn lint_program_with_ast_view( 34 | &self, 35 | context: &mut Context, 36 | program: Program, 37 | ) { 38 | NoUnsafeNegationHandler.traverse(program, context); 39 | } 40 | } 41 | 42 | struct NoUnsafeNegationHandler; 43 | 44 | impl Handler for NoUnsafeNegationHandler { 45 | fn bin_expr(&mut self, bin_expr: &ast_view::BinExpr, ctx: &mut Context) { 46 | use deno_ast::view::{BinaryOp, Expr, UnaryOp}; 47 | if_chain! { 48 | if matches!(bin_expr.op(), BinaryOp::In | BinaryOp::InstanceOf); 49 | if let Expr::Unary(unary_expr) = &bin_expr.left; 50 | if unary_expr.op() == UnaryOp::Bang; 51 | then { 52 | ctx.add_diagnostic_with_hint( 53 | bin_expr.range(), 54 | CODE, 55 | NoUnsafeNegationMessage::Unexpected(bin_expr.op().to_string()), 56 | HINT, 57 | ); 58 | } 59 | } 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use super::*; 66 | 67 | #[test] 68 | fn no_unsafe_negation_valid() { 69 | assert_lint_ok! { 70 | NoUnsafeNegation, 71 | "1 in [1, 2, 3]", 72 | "key in object", 73 | "foo instanceof Date", 74 | "!(1 in [1, 2, 3])", 75 | "!(key in object)", 76 | "!(foo instanceof Date)", 77 | "(!key) in object", 78 | "(!foo) instanceof Date", 79 | }; 80 | } 81 | 82 | #[test] 83 | fn no_unsafe_negation_invalid() { 84 | assert_lint_err! { 85 | NoUnsafeNegation, 86 | "!1 in [1, 2, 3]": [ 87 | { 88 | col: 0, 89 | message: variant!(NoUnsafeNegationMessage, Unexpected, "in"), 90 | hint: HINT 91 | } 92 | ], 93 | "!key in object": [ 94 | { 95 | col: 0, 96 | message: variant!(NoUnsafeNegationMessage, Unexpected, "in"), 97 | hint: HINT 98 | } 99 | ], 100 | "!foo instanceof Date": [ 101 | { 102 | col: 0, 103 | message: variant!(NoUnsafeNegationMessage, Unexpected, "instanceof"), 104 | hint: HINT 105 | } 106 | ], 107 | }; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/rules/no_unused_labels.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use super::{Context, LintRule}; 4 | use crate::handler::{Handler, Traverse}; 5 | use crate::tags::{self, Tags}; 6 | use crate::Program; 7 | use deno_ast::{view as ast_view, SourceRanged}; 8 | use derive_more::Display; 9 | use if_chain::if_chain; 10 | 11 | #[derive(Debug)] 12 | pub struct NoUnusedLabels; 13 | 14 | const CODE: &str = "no-unused-labels"; 15 | 16 | #[derive(Display)] 17 | enum NoUnusedLabelsMessage { 18 | #[display(fmt = "`{}` label is never used", _0)] 19 | Unused(String), 20 | } 21 | 22 | impl LintRule for NoUnusedLabels { 23 | fn tags(&self) -> Tags { 24 | &[tags::RECOMMENDED] 25 | } 26 | 27 | fn code(&self) -> &'static str { 28 | CODE 29 | } 30 | 31 | fn lint_program_with_ast_view( 32 | &self, 33 | context: &mut Context, 34 | program: Program, 35 | ) { 36 | let mut handler = NoUnusedLabelsHandler::default(); 37 | handler.traverse(program, context); 38 | } 39 | } 40 | 41 | struct Label { 42 | used: bool, 43 | name: String, 44 | } 45 | 46 | #[derive(Default)] 47 | struct NoUnusedLabelsHandler { 48 | labels: Vec