├── .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#""#: [
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#""#: [
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