├── benchmarks
├── .gitignore
├── package.json
├── benchmarks.ts
└── .eslintrc.js
├── .gitignore
├── .rustfmt.toml
├── examples
└── dlint
│ ├── testdata
│ ├── simple.ts
│ ├── issue1145_no_trailing_newline.ts
│ ├── issue1145_no_trailing_newline.out
│ └── simple.out
│ ├── example_config.json
│ ├── diagnostics.rs
│ └── rules.rs
├── rust-toolchain.toml
├── .github
├── ISSUE_TEMPLATE
│ ├── blank_issue.md
│ └── bug_report.md
└── workflows
│ ├── publish.yml
│ ├── release.yml
│ └── ci.yml
├── .gitmodules
├── deno.json
├── .vscode
└── settings.json
├── schemas
├── tags.v1.json
└── rules.v1.json
├── .clippy.toml
├── .editorconfig
├── src
├── ast_parser.rs
├── tags.rs
├── rules
│ ├── ban_unused_ignore.rs
│ ├── ban_unknown_rule_code.rs
│ ├── no_with.rs
│ ├── no_sparse_arrays.rs
│ ├── no_debugger.rs
│ ├── no_new_symbol.rs
│ ├── no_delete_var.rs
│ ├── no_octal.rs
│ ├── ban_untagged_ignore.rs
│ ├── jsx_no_comment_text_nodes.rs
│ ├── no_var.rs
│ ├── react_no_danger.rs
│ ├── prefer_namespace_keyword.rs
│ ├── jsx_no_children_prop.rs
│ ├── no_empty_enum.rs
│ ├── single_var_declarator.rs
│ ├── no_throw_literal.rs
│ ├── jsx_no_unescaped_entities.rs
│ ├── jsx_no_duplicate_props.rs
│ ├── no_explicit_any.rs
│ ├── no_unsafe_negation.rs
│ ├── no_top_level_await.rs
│ ├── react_no_danger_with_children.rs
│ ├── no_empty_interface.rs
│ ├── jsx_no_useless_fragment.rs
│ ├── no_console.rs
│ ├── no_prototype_builtins.rs
│ ├── ban_untagged_todo.rs
│ ├── no_async_promise_executor.rs
│ ├── no_this_alias.rs
│ ├── no_obj_calls.rs
│ ├── no_useless_rename.rs
│ ├── no_boolean_literal_for_arguments.rs
│ ├── no_array_constructor.rs
│ ├── no_import_prefix.rs
│ ├── explicit_function_return_type.rs
│ ├── jsx_props_no_spread_multi.rs
│ ├── triple_slash_reference.rs
│ ├── jsx_boolean_value.rs
│ ├── no_await_in_sync_fn.rs
│ ├── fresh_handler_export.rs
│ ├── prefer_ascii.rs
│ ├── no_namespace.rs
│ ├── no_non_null_assertion.rs
│ ├── no_window.rs
│ ├── no_empty_character_class.rs
│ ├── no_unused_labels.rs
│ ├── no_unversioned_import.rs
│ ├── no_global_assign.rs
│ ├── no_import_assertions.rs
│ ├── guard_for_in.rs
│ ├── no_non_null_asserted_optional_chain.rs
│ ├── jsx_void_dom_elements_no_children.rs
│ ├── no_eval.rs
│ └── no_external_imports.rs
├── performance_mark.rs
└── diagnostic.rs
├── www
└── main.ts
├── tools
├── generate_no_window_prefix_deny_list.ts
├── lint.ts
├── format.ts
└── tests
│ └── scaffold_integration_test.ts
├── LICENSE
└── Cargo.toml
/benchmarks/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 |
3 | /target
4 |
--------------------------------------------------------------------------------
/.rustfmt.toml:
--------------------------------------------------------------------------------
1 | max_width = 80
2 | tab_spaces = 2
3 | edition = "2018"
--------------------------------------------------------------------------------
/examples/dlint/testdata/simple.ts:
--------------------------------------------------------------------------------
1 | function hello(): any {
2 |
3 | }
--------------------------------------------------------------------------------
/examples/dlint/testdata/issue1145_no_trailing_newline.ts:
--------------------------------------------------------------------------------
1 | /*---
2 | ---*/
3 | var base
--------------------------------------------------------------------------------
/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "1.90.0"
3 | components = ["clippy", "rustfmt"]
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/blank_issue.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Blank Issue
3 | about: Create a blank issue.
4 | ---
5 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "benchmarks/oak"]
2 | path = benchmarks/oak
3 | url = https://github.com/oakserver/oak.git
4 |
--------------------------------------------------------------------------------
/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "lock": false,
3 | "exclude": [
4 | "target",
5 | "examples",
6 | "benchmarks/oak"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "deno.enable": true,
3 | "deno.lint": true,
4 | "deno.unstable": true,
5 | "deno.config": "./www/deno.json"
6 | }
7 |
--------------------------------------------------------------------------------
/schemas/tags.v1.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "enum": ["fresh", "jsr", "jsx", "react", "recommended", "workspace"]
4 | }
5 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: publish
2 |
3 | on:
4 | push:
5 | tags:
6 | - "*"
7 |
8 | jobs:
9 | publish:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: read
13 | id-token: write
14 | steps:
15 | - name: Clone repository
16 | uses: actions/checkout@v5
17 | - uses: rust-lang/crates-io-auth-action@v1
18 | id: auth
19 | - run: cargo publish
20 | env:
21 | CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
22 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/www/main.ts:
--------------------------------------------------------------------------------
1 | const rulePat = new URLPattern({
2 | pathname: "/rules/:rule",
3 | }, {
4 | ignoreCase: true,
5 | });
6 |
7 | Deno.serve((req) => {
8 | const url = new URL(req.url);
9 |
10 | const ruleMatch = rulePat.exec(req.url);
11 | const maybeRule = ruleMatch?.pathname.groups.rule;
12 |
13 | if (maybeRule) {
14 | return Response.redirect(
15 | `https://docs.deno.com/lint/rules/${maybeRule}`,
16 | 301,
17 | );
18 | }
19 |
20 | if (url.pathname.startsWith("/ignoring-rules")) {
21 | // TODO(bartlomieju): verify the anchor is not changed or use
22 | // "go" url
23 | return Response.redirect(
24 | `https://docs.deno.com/go/lint-ignore`,
25 | 301,
26 | );
27 | }
28 |
29 | return Response.redirect(
30 | "https://docs.deno.com/lint/",
31 | 301,
32 | );
33 | });
34 |
--------------------------------------------------------------------------------
/src/tags.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2 |
3 | use std::fmt::Display;
4 |
5 | #[derive(Debug, Hash, PartialEq)]
6 | pub struct Tag(&'static str);
7 |
8 | pub type Tags = &'static [Tag];
9 |
10 | impl Display for Tag {
11 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12 | write!(f, "{}", self.0)
13 | }
14 | }
15 |
16 | impl Tag {
17 | pub fn display(&self) -> &'static str {
18 | self.0
19 | }
20 | }
21 |
22 | pub const RECOMMENDED: Tag = Tag("recommended");
23 | pub const FRESH: Tag = Tag("fresh");
24 | pub const JSR: Tag = Tag("jsr");
25 | pub const REACT: Tag = Tag("react");
26 | pub const JSX: Tag = Tag("jsx");
27 | pub const WORKSPACE: Tag = Tag("workspace");
28 |
29 | pub const ALL_TAGS: Tags = &[RECOMMENDED, FRESH, JSR, REACT, JSX, WORKSPACE];
30 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/tools/generate_no_window_prefix_deny_list.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2 | import { doc } from "https://deno.land/x/deno_doc@v0.13.0/mod.ts";
3 |
4 | const windowDoc = await doc(
5 | "https://raw.githubusercontent.com/denoland/deno/main/cli/dts/lib.dom.d.ts",
6 | );
7 | const workerDoc = await doc(
8 | "https://raw.githubusercontent.com/denoland/deno/main/cli/dts/lib.webworker.d.ts",
9 | );
10 |
11 | const windowItems = new Set(windowDoc.map((item) => item.name));
12 | const workerItems = new Set(workerDoc.map((item) => item.name));
13 |
14 | const intersection = new Set(
15 | [...windowItems].filter((x) => workerItems.has(x)),
16 | );
17 | intersection.add("Deno");
18 |
19 | // window's `location` and worker's `location` are not the same
20 | // https://github.com/denoland/deno_lint/pull/824#issuecomment-908820143
21 | intersection.delete("location");
22 |
23 | console.log(JSON.stringify([...intersection], null, 2));
24 |
--------------------------------------------------------------------------------
/.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@v5
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 jsr:@deno/rust-automation@0.22.0/tasks/publish-release --${{github.event.inputs.releaseKind}}
38 |
--------------------------------------------------------------------------------
/tools/lint.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env -S deno run --allow-run
2 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
3 | const release = Deno.args.includes("--release");
4 | console.log("clippy");
5 |
6 | const mode = release ? ["--release"] : [];
7 | const clippy = [
8 | "clippy",
9 | "--all-targets",
10 | "--all-features",
11 | ...mode,
12 | "--locked",
13 | "--",
14 | "-D",
15 | "clippy::all",
16 | ];
17 |
18 | const p1 = new Deno.Command("cargo", {
19 | args: clippy,
20 | stdin: "null",
21 | });
22 |
23 | const o1 = await p1.output();
24 |
25 | if (o1.code !== 0) {
26 | throw new Error(`Failed: ${clippy.join(" ")}`);
27 | }
28 |
29 | console.log("deno lint");
30 |
31 | const cargoTargetDir = Deno.env.get("CARGO_TARGET_DIR") || "./target";
32 | const dlint = `${cargoTargetDir}/${
33 | release ? "release" : "debug"
34 | }/examples/dlint`;
35 | const p2 = new Deno.Command(dlint, {
36 | args: ["run", "benchmarks/benchmarks.ts"],
37 | stdin: "null",
38 | });
39 |
40 | const o2 = await p2.output();
41 |
42 | if (o2.code !== 0) {
43 | throw new Error(`Failed: ${dlint} benchmarks/benchmarks.ts`);
44 | }
45 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tools/format.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env -S deno run --allow-run
2 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
3 | const check = Deno.args.includes("--check");
4 | console.log("rustfmt");
5 |
6 | const checkArgs = check ? ["--check"] : [];
7 |
8 | const p1 = new Deno.Command("rustfmt", {
9 | args: [...checkArgs, "examples/dlint/main.rs"],
10 | stdin: "null",
11 | }).spawn();
12 |
13 | const result1 = await p1.status;
14 |
15 | if (!result1.success) {
16 | throw new Error(
17 | `Failed: rustfmt ${check ? "--check" : ""}`,
18 | );
19 | }
20 |
21 | const p2 = new Deno.Command("rustfmt", {
22 | args: [...checkArgs, "src/lib.rs"],
23 | stdin: "null",
24 | }).spawn();
25 |
26 | const result2 = await p2.status;
27 |
28 | if (!result2.success) {
29 | throw new Error(`Failed: rustfmt ${check ? "--check" : ""}`);
30 | }
31 |
32 | console.log("deno fmt");
33 |
34 | const p3 = new Deno.Command("deno", {
35 | args: [
36 | "fmt",
37 | ...checkArgs,
38 | ],
39 | stdin: "null",
40 | }).spawn();
41 |
42 | const result3 = await p3.status;
43 |
44 | if (!result3.success) {
45 | throw new Error(
46 | `Failed: deno fmt ${check ? "--check" : ""}`,
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "deno_lint"
3 | version = "0.82.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.52.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 | deno_semver = "0.9.0"
37 |
38 | [dev-dependencies]
39 | ansi_term = "0.12.1"
40 | atty = "0.2.14"
41 | clap = { version = "3", features = ["cargo"] }
42 | env_logger = "0.10.1"
43 | globwalk = "0.9.1"
44 | os_pipe = "1.1.5"
45 | pulldown-cmark = "0.9.3"
46 | rayon = "1.8.0"
47 | console_static_text = "0.8.2"
48 |
--------------------------------------------------------------------------------
/src/rules/no_with.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;
8 | use deno_ast::SourceRanged;
9 |
10 | #[derive(Debug)]
11 | pub struct NoWith;
12 |
13 | const CODE: &str = "no-with";
14 | const MESSAGE: &str = "`with` statement is not allowed";
15 |
16 | impl LintRule for NoWith {
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 | NoWithHandler.traverse(program, context);
31 | }
32 | }
33 |
34 | struct NoWithHandler;
35 |
36 | impl Handler for NoWithHandler {
37 | fn with_stmt(&mut self, with_stmt: &ast_view::WithStmt, ctx: &mut Context) {
38 | ctx.add_diagnostic(with_stmt.range(), CODE, MESSAGE);
39 | }
40 | }
41 |
42 | #[cfg(test)]
43 | mod tests {
44 | use super::*;
45 |
46 | #[test]
47 | fn no_with_invalid() {
48 | assert_lint_err! {
49 | NoWith,
50 | "with (someVar) { console.log('asdf'); }": [{ col: 0, message: MESSAGE }],
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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_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_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_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 |
--------------------------------------------------------------------------------
/tools/tests/scaffold_integration_test.ts:
--------------------------------------------------------------------------------
1 | import { exists } from "https://deno.land/std@0.106.0/fs/mod.ts";
2 | import { assertEquals } from "https://deno.land/std@0.106.0/testing/asserts.ts";
3 |
4 | Deno.test(
5 | "Check if the files created by tools/scaffold.ts pass `cargo check`",
6 | async () => {
7 | const name = "dummy-lint-rule-for-testing";
8 | const filename = name.replaceAll("-", "_");
9 | const rulesPath = "./src/rules.rs";
10 |
11 | // Preserve the original content of src/rules.rs
12 | const rulesRs = await Deno.readTextFile(rulesPath);
13 |
14 | try {
15 | console.log(`Run the scaffold script to create ${name} rule`);
16 | const p1 = new Deno.Command("deno", {
17 | args: [
18 | "run",
19 | "--allow-write=.",
20 | "--allow-read=.",
21 | "./tools/scaffold.ts",
22 | name,
23 | ],
24 | });
25 | const o1 = await p1.output();
26 |
27 | assertEquals(o1.code, 0);
28 | console.log("Scaffold succeeded");
29 |
30 | // Check if `cargo check` passes
31 | console.log("Run `cargo check`");
32 | const args = ["check", "--all-targets", "--all-features", "--locked"];
33 | if (Deno.env.get("GH_ACTIONS") === "1") {
34 | // do a release build on GitHub actions since the other
35 | // cargo builds are also release
36 | args.push("--release");
37 | }
38 | const p2 = new Deno.Command("cargo", {
39 | args,
40 | });
41 | const o2 = await p2.output();
42 |
43 | assertEquals(o2.code, 0);
44 | console.log("`cargo check` succeeded");
45 | } finally {
46 | console.log("Start cleanup...");
47 | console.log("Restoring src/rules.rs...");
48 | await Deno.writeTextFile(rulesPath, rulesRs);
49 |
50 | console.log(`Deleting src/rules/${filename}.rs...`);
51 | const rsPath = `./src/rules/${filename}.rs`;
52 | if (await exists(rsPath)) {
53 | await Deno.remove(rsPath);
54 | }
55 |
56 | console.log("Cleanup finished");
57 | }
58 | },
59 | );
60 |
--------------------------------------------------------------------------------
/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/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/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/no_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::{NodeKind, NodeTrait, VarDecl, VarDeclKind};
8 | use deno_ast::SourceRangedForSpanned;
9 |
10 | #[derive(Debug)]
11 | pub struct NoVar;
12 |
13 | const MESSAGE: &str = "`var` keyword is not allowed.";
14 | const CODE: &str = "no-var";
15 |
16 | impl LintRule for NoVar {
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 | NoVarHandler.traverse(program, context);
31 | }
32 | }
33 |
34 | struct NoVarHandler;
35 |
36 | impl Handler for NoVarHandler {
37 | fn var_decl(&mut self, var_decl: &VarDecl, ctx: &mut Context) {
38 | if var_decl.parent().kind() == NodeKind::TsModuleBlock {
39 | return;
40 | }
41 |
42 | if var_decl.decl_kind() == VarDeclKind::Var {
43 | let range = var_decl.tokens().first().unwrap().range();
44 | ctx.add_diagnostic(range, CODE, MESSAGE);
45 | }
46 | }
47 | }
48 |
49 | #[cfg(test)]
50 | mod tests {
51 | use super::*;
52 |
53 | #[test]
54 | fn no_var_valid() {
55 | assert_lint_ok!(
56 | NoVar,
57 | r#"let foo = 0; const bar = 1"#,
58 | r#"declare global {
59 | namespace globalThis {
60 | var test: string
61 | }
62 | }"#,
63 | r#"declare global {
64 | var test: string
65 | }"#,
66 | );
67 | }
68 |
69 | #[test]
70 | fn no_var_invalid() {
71 | assert_lint_err!(
72 | NoVar,
73 | "var foo = 0;": [{
74 | col: 0,
75 | message: MESSAGE,
76 | }],
77 | "let foo = 0; var bar = 1;": [{
78 | col: 13,
79 | message: MESSAGE,
80 | }],
81 | "let foo = 0; var bar = 1; var x = 2;": [
82 | {
83 | col: 13,
84 | message: MESSAGE,
85 | },
86 | {
87 | col: 26,
88 | message: MESSAGE,
89 | }
90 | ],
91 | );
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/rules/react_no_danger.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::{JSXAttr, JSXAttrName};
8 | use deno_ast::SourceRanged;
9 |
10 | #[derive(Debug)]
11 | pub struct ReactNoDanger;
12 |
13 | const CODE: &str = "react-no-danger";
14 |
15 | impl LintRule for ReactNoDanger {
16 | fn tags(&self) -> Tags {
17 | &[tags::REACT, 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 | NoDangerHandler.traverse(program, context);
30 | }
31 | }
32 |
33 | const MESSAGE: &str = "Do not use `dangerouslySetInnerHTML`";
34 | const HINT: &str = "Remove this attribute";
35 |
36 | struct NoDangerHandler;
37 |
38 | impl Handler for NoDangerHandler {
39 | fn jsx_attr(&mut self, node: &JSXAttr, ctx: &mut Context) {
40 | if let JSXAttrName::Ident(name) = node.name {
41 | if name.sym() == "dangerouslySetInnerHTML" {
42 | ctx.add_diagnostic_with_hint(name.range(), CODE, MESSAGE, HINT);
43 | }
44 | }
45 | }
46 | }
47 |
48 | // most tests are taken from ESlint, commenting those
49 | // requiring code path support
50 | #[cfg(test)]
51 | mod tests {
52 | use super::*;
53 |
54 | #[test]
55 | fn no_danger_valid() {
56 | assert_lint_ok! {
57 | ReactNoDanger,
58 | filename: "file:///foo.jsx",
59 | // non derived classes.
60 | r#""#,
61 | };
62 | }
63 |
64 | #[test]
65 | fn no_danger_invalid() {
66 | assert_lint_err! {
67 | ReactNoDanger,
68 | filename: "file:///foo.jsx",
69 | "": [
70 | {
71 | col: 5,
72 | message: MESSAGE,
73 | hint: HINT,
74 | }
75 | ],
76 | r#""#: [
77 | {
78 | col: 5,
79 | message: MESSAGE,
80 | hint: HINT,
81 | }
82 | ],
83 | "": [
84 | {
85 | col: 5,
86 | message: MESSAGE,
87 | hint: HINT,
88 | }
89 | ]
90 | };
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/rules/prefer_namespace_keyword.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::{TsModuleDecl, TsModuleName};
8 | use deno_ast::SourceRanged;
9 | use once_cell::sync::Lazy;
10 | use regex::Regex;
11 |
12 | #[derive(Debug)]
13 | pub struct PreferNamespaceKeyword;
14 |
15 | const CODE: &str = "prefer-namespace-keyword";
16 | const MESSAGE: &str = "`module` keyword in module declaration is not allowed";
17 |
18 | impl LintRule for PreferNamespaceKeyword {
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 | PreferNamespaceKeywordHandler.traverse(program, context);
33 | }
34 | }
35 |
36 | struct PreferNamespaceKeywordHandler;
37 |
38 | impl Handler for PreferNamespaceKeywordHandler {
39 | fn ts_module_decl(&mut self, mod_decl: &TsModuleDecl, ctx: &mut Context) {
40 | if let TsModuleName::Str(_) = &mod_decl.id {
41 | return;
42 | }
43 | static KEYWORD: Lazy =
44 | Lazy::new(|| Regex::new(r"(declare\s)?(?P\w+)").unwrap());
45 |
46 | let snippet = mod_decl.text_fast(ctx.text_info());
47 | if let Some(capt) = KEYWORD.captures(snippet) {
48 | let keyword = capt.name("keyword").unwrap().as_str();
49 | if keyword == "module" && !mod_decl.global() {
50 | ctx.add_diagnostic(mod_decl.range(), CODE, MESSAGE)
51 | }
52 | }
53 | }
54 | }
55 |
56 | #[cfg(test)]
57 | mod tests {
58 | use super::*;
59 |
60 | #[test]
61 | fn prefer_namespace_keyword_valid() {
62 | assert_lint_ok! {
63 | PreferNamespaceKeyword,
64 | "declare module 'foo';",
65 | "declare module 'foo' {}",
66 | "namespace foo {}",
67 | "declare namespace foo {}",
68 | "declare global {}",
69 | };
70 | }
71 |
72 | #[test]
73 | fn prefer_namespace_keyword_invalid() {
74 | assert_lint_err! {
75 | PreferNamespaceKeyword,
76 | r#"module foo {}"#: [{ col: 0, message: MESSAGE }],
77 | r#"
78 | declare module foo {
79 | declare module bar {}
80 | }"#: [{ line: 2, col: 6, message: MESSAGE}, { line: 3, col: 8, message: MESSAGE }],
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/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/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/single_var_declarator.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2 |
3 | use super::program_ref;
4 | use super::{Context, LintRule};
5 | use crate::Program;
6 | use crate::ProgramRef;
7 | use deno_ast::swc::ast::VarDecl;
8 | use deno_ast::swc::ecma_visit::noop_visit_type;
9 | use deno_ast::swc::ecma_visit::Visit;
10 | use deno_ast::SourceRangedForSpanned;
11 | use derive_more::Display;
12 |
13 | #[derive(Debug)]
14 | pub struct SingleVarDeclarator;
15 |
16 | const CODE: &str = "single-var-declarator";
17 |
18 | #[derive(Display)]
19 | enum SingleVarDeclaratorMessage {
20 | #[display(fmt = "Multiple variable declarators are not allowed")]
21 | Unexpected,
22 | }
23 |
24 | impl LintRule for SingleVarDeclarator {
25 | fn code(&self) -> &'static str {
26 | CODE
27 | }
28 |
29 | fn lint_program_with_ast_view<'view>(
30 | &self,
31 | context: &mut Context<'view>,
32 | program: Program<'view>,
33 | ) {
34 | let program = program_ref(program);
35 | let mut visitor = SingleVarDeclaratorVisitor::new(context);
36 | match program {
37 | ProgramRef::Module(m) => visitor.visit_module(m),
38 | ProgramRef::Script(s) => visitor.visit_script(s),
39 | }
40 | }
41 | }
42 |
43 | struct SingleVarDeclaratorVisitor<'c, 'view> {
44 | context: &'c mut Context<'view>,
45 | }
46 |
47 | impl<'c, 'view> SingleVarDeclaratorVisitor<'c, 'view> {
48 | fn new(context: &'c mut Context<'view>) -> Self {
49 | Self { context }
50 | }
51 | }
52 |
53 | impl Visit for SingleVarDeclaratorVisitor<'_, '_> {
54 | noop_visit_type!();
55 |
56 | fn visit_var_decl(&mut self, var_decl: &VarDecl) {
57 | if var_decl.decls.len() > 1 {
58 | self.context.add_diagnostic(
59 | var_decl.range(),
60 | CODE,
61 | SingleVarDeclaratorMessage::Unexpected,
62 | );
63 | }
64 | }
65 | }
66 |
67 | #[cfg(test)]
68 | mod tests {
69 | use super::*;
70 |
71 | #[test]
72 | fn single_var_declarator_invalid() {
73 | assert_lint_err! {
74 | SingleVarDeclarator,
75 | r#"const a1 = "a", b1 = "b", c1 = "c";"#: [
76 | {
77 | col: 0,
78 | message: SingleVarDeclaratorMessage::Unexpected,
79 | }],
80 | r#"let a2 = "a", b2 = "b", c2 = "c";"#: [
81 | {
82 | col: 0,
83 | message: SingleVarDeclaratorMessage::Unexpected,
84 | }],
85 | r#"var a3 = "a", b3 = "b", c3 = "c";"#: [
86 | {
87 | col: 0,
88 | message: SingleVarDeclaratorMessage::Unexpected,
89 | }],
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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_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/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_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_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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/rules/react_no_danger_with_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::{
8 | JSXAttrName, JSXAttrOrSpread, JSXElement, JSXElementChild,
9 | };
10 | use deno_ast::SourceRanged;
11 | use once_cell::sync::Lazy;
12 |
13 | #[derive(Debug)]
14 | pub struct ReactNoDangerWithChildren;
15 |
16 | const CODE: &str = "react-no-danger-with-children";
17 |
18 | impl LintRule for ReactNoDangerWithChildren {
19 | fn tags(&self) -> Tags {
20 | &[tags::REACT, 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 | JSXNoDangerWithChildrenHandler.traverse(program, context);
33 | }
34 | }
35 |
36 | const MESSAGE: &str =
37 | "Using JSX children together with 'dangerouslySetInnerHTML' is invalid";
38 | const HINT: &str = "Remove the JSX children";
39 |
40 | static IGNORE_TEXT: Lazy =
41 | Lazy::new(|| regex::Regex::new(r#"^\n\s+$"#).unwrap());
42 |
43 | struct JSXNoDangerWithChildrenHandler;
44 |
45 | impl Handler for JSXNoDangerWithChildrenHandler {
46 | fn jsx_element(&mut self, node: &JSXElement, ctx: &mut Context) {
47 | for attr in node.opening.attrs {
48 | if let JSXAttrOrSpread::JSXAttr(attr) = attr {
49 | if let JSXAttrName::Ident(id) = attr.name {
50 | if id.sym() == "dangerouslySetInnerHTML" {
51 | let filtered = node
52 | .children
53 | .iter()
54 | .filter(|child| {
55 | if let JSXElementChild::JSXText(text) = child {
56 | if IGNORE_TEXT.is_match(text.value()) {
57 | return false;
58 | }
59 | }
60 |
61 | true
62 | })
63 | .collect::>();
64 |
65 | if !filtered.is_empty() {
66 | ctx.add_diagnostic_with_hint(id.range(), CODE, MESSAGE, HINT);
67 | }
68 | }
69 | }
70 | }
71 | }
72 | }
73 | }
74 |
75 | #[cfg(test)]
76 | mod tests {
77 | use super::*;
78 |
79 | #[test]
80 | fn jsx_no_danger_with_children_valid() {
81 | assert_lint_ok! {
82 | ReactNoDangerWithChildren,
83 | filename: "file:///foo.jsx",
84 | r#""#,
85 | r#""#,
86 | r#"
87 |
"#,
88 | };
89 | }
90 |
91 | #[test]
92 | fn jsx_no_danger_with_children_invalid() {
93 | assert_lint_err! {
94 | ReactNoDangerWithChildren,
95 | filename: "file:///foo.jsx",
96 | r#"foo
"#: [
97 | {
98 | col: 5,
99 | message: MESSAGE,
100 | hint: HINT
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/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/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_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/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/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_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_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_useless_rename.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::{
7 | ExportNamedSpecifier, ImportNamedSpecifier, ModuleExportName, ObjectPat,
8 | ObjectPatProp, Pat, PropName,
9 | };
10 | use deno_ast::SourceRanged;
11 |
12 | #[derive(Debug)]
13 | pub struct NoUselessRename;
14 |
15 | const MESSAGE: &str = "The original name is exactly the same as the new name.";
16 | const HINT: &str = "Remove the rename operation.";
17 | const CODE: &str = "no-useless-rename";
18 |
19 | impl LintRule for NoUselessRename {
20 | fn tags(&self) -> Tags {
21 | &[]
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 | NoUselessRenameHandler.traverse(program, context);
34 | }
35 | }
36 |
37 | struct NoUselessRenameHandler;
38 |
39 | impl Handler for NoUselessRenameHandler {
40 | fn import_named_specifier(
41 | &mut self,
42 | node: &ImportNamedSpecifier,
43 | ctx: &mut Context,
44 | ) {
45 | if let Some(ModuleExportName::Ident(imported_name)) = node.imported {
46 | if imported_name.sym() == node.local.sym() {
47 | ctx.add_diagnostic_with_hint(node.range(), CODE, MESSAGE, HINT);
48 | }
49 | }
50 | }
51 |
52 | fn object_pat(&mut self, node: &ObjectPat, ctx: &mut Context) {
53 | for prop in node.props {
54 | let ObjectPatProp::KeyValue(key_val) = prop else {
55 | return;
56 | };
57 |
58 | let PropName::Ident(prop_key) = key_val.key else {
59 | return;
60 | };
61 |
62 | let Pat::Ident(prop_value) = key_val.value else {
63 | return;
64 | };
65 |
66 | if prop_value.id.sym() == prop_key.sym() {
67 | ctx.add_diagnostic_with_hint(node.range(), CODE, MESSAGE, HINT);
68 | }
69 | }
70 | }
71 |
72 | fn export_named_specifier(
73 | &mut self,
74 | node: &ExportNamedSpecifier,
75 | ctx: &mut Context,
76 | ) {
77 | let Some(exported) = node.exported else {
78 | return;
79 | };
80 |
81 | let ModuleExportName::Ident(exported_id) = exported else {
82 | return;
83 | };
84 |
85 | let ModuleExportName::Ident(original) = node.orig else {
86 | return;
87 | };
88 |
89 | if exported_id.sym() == original.sym() {
90 | ctx.add_diagnostic_with_hint(node.range(), CODE, MESSAGE, HINT);
91 | }
92 | }
93 | }
94 |
95 | #[cfg(test)]
96 | mod tests {
97 | use super::*;
98 |
99 | #[test]
100 | fn console_allowed() {
101 | assert_lint_ok!(
102 | NoUselessRename,
103 | r#"import { foo as bar } from "foo";"#,
104 | r#"const { foo: bar } = obj;"#,
105 | r#"export { foo as bar };"#,
106 | );
107 | }
108 |
109 | #[test]
110 | fn no_console_invalid() {
111 | assert_lint_err!(
112 | NoUselessRename,
113 | r#"import { foo as foo } from "foo";"#: [{
114 | col: 9,
115 | message: MESSAGE,
116 | hint: HINT,
117 | }],
118 | r#"const { foo: foo } = obj;"#: [{
119 | col: 6,
120 | message: MESSAGE,
121 | hint: HINT,
122 | }],
123 | r#"export { foo as foo };"#: [{
124 | col: 9,
125 | message: MESSAGE,
126 | hint: HINT,
127 | }]
128 | );
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/.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-latest]
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@v5
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@v2
38 | with:
39 | deno-version: canary
40 |
41 | - name: Install Node.js
42 | if: contains(matrix.os, 'ubuntu')
43 | uses: actions/setup-node@v5
44 | with:
45 | node-version: "20"
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 |
--------------------------------------------------------------------------------
/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_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 |
--------------------------------------------------------------------------------
/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-import-prefix",
74 | "no-inferrable-types",
75 | "no-inner-declarations",
76 | "no-invalid-regexp",
77 | "no-invalid-triple-slash-reference",
78 | "no-irregular-whitespace",
79 | "no-misused-new",
80 | "no-namespace",
81 | "no-new-symbol",
82 | "no-node-globals",
83 | "no-non-null-asserted-optional-chain",
84 | "no-non-null-assertion",
85 | "no-obj-calls",
86 | "no-octal",
87 | "no-process-global",
88 | "no-prototype-builtins",
89 | "no-redeclare",
90 | "no-regex-spaces",
91 | "no-self-assign",
92 | "no-self-compare",
93 | "no-setter-return",
94 | "no-shadow-restricted-names",
95 | "no-sloppy-imports",
96 | "no-slow-types",
97 | "no-sparse-arrays",
98 | "no-sync-fn-in-async-fn",
99 | "no-this-alias",
100 | "no-this-before-super",
101 | "no-throw-literal",
102 | "no-top-level-await",
103 | "no-undef",
104 | "no-unreachable",
105 | "no-unsafe-finally",
106 | "no-unsafe-negation",
107 | "no-unused-labels",
108 | "no-unused-vars",
109 | "no-unversioned-import",
110 | "no-useless-rename",
111 | "no-var",
112 | "no-window",
113 | "no-window-prefix",
114 | "no-with",
115 | "prefer-as-const",
116 | "prefer-ascii",
117 | "prefer-const",
118 | "prefer-namespace-keyword",
119 | "prefer-primordials",
120 | "require-await",
121 | "require-yield",
122 | "rules-of-hooks",
123 | "single-var-declarator",
124 | "triple-slash-reference",
125 | "use-isnan",
126 | "valid-typeof",
127 | "verbatim-module-syntax"
128 | ]
129 | }
130 |
--------------------------------------------------------------------------------
/src/rules/no_import_prefix.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, WORKSPACE};
6 | use crate::Program;
7 | use deno_ast::view::{CallExpr, Callee, Expr, ImportDecl, Lit};
8 | use deno_ast::SourceRanged;
9 |
10 | #[derive(Debug)]
11 | pub struct NoImportPrefix;
12 |
13 | const CODE: &str = "no-import-prefix";
14 | const MESSAGE: &str =
15 | "Inline 'npm:', 'jsr:' or 'https:' dependency not allowed";
16 | const HINT: &str = "Add it as a dependency in a deno.json or package.json instead and reference it here via its bare specifier";
17 |
18 | impl LintRule for NoImportPrefix {
19 | fn tags(&self) -> Tags {
20 | &[WORKSPACE]
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 | NoImportPrefixHandler.traverse(program, context);
33 | }
34 | }
35 |
36 | struct NoImportPrefixHandler;
37 |
38 | impl Handler for NoImportPrefixHandler {
39 | fn import_decl(&mut self, node: &ImportDecl, ctx: &mut Context) {
40 | if is_non_bare(&node.src.value().to_string_lossy()) {
41 | ctx.add_diagnostic_with_hint(node.src.range(), CODE, MESSAGE, HINT);
42 | }
43 | }
44 |
45 | fn call_expr(&mut self, node: &CallExpr, ctx: &mut Context) {
46 | if let Callee::Import(_) = node.callee {
47 | if let Some(arg) = node.args.first() {
48 | if let Expr::Lit(Lit::Str(lit)) = arg.expr {
49 | if is_non_bare(&lit.value().to_string_lossy()) {
50 | ctx.add_diagnostic_with_hint(arg.range(), CODE, MESSAGE, HINT);
51 | }
52 | }
53 | }
54 | }
55 | }
56 | }
57 |
58 | fn is_non_bare(s: &str) -> bool {
59 | s.starts_with("npm:")
60 | || s.starts_with("jsr:")
61 | || s.starts_with("http:")
62 | || s.starts_with("https:")
63 | }
64 |
65 | #[cfg(test)]
66 | mod tests {
67 | use super::*;
68 |
69 | #[test]
70 | fn no_with_valid() {
71 | assert_lint_ok! {
72 | NoImportPrefix,
73 | r#"import foo from "foo";"#,
74 | r#"import foo from "@foo/bar";"#,
75 | r#"import foo from "./foo";"#,
76 | r#"import foo from "../foo";"#,
77 | r#"import foo from "~/foo";"#,
78 | r#"import("foo")"#,
79 | r#"import("@foo/bar")"#,
80 | r#"import("./foo")"#,
81 | r#"import("../foo")"#,
82 | r#"import("~/foo")"#,
83 | }
84 | }
85 |
86 | #[test]
87 | fn no_with_invalid() {
88 | assert_lint_err! {
89 | NoImportPrefix,
90 | r#"import foo from "jsr:@foo/foo";"#: [{
91 | col: 16,
92 | message: MESSAGE,
93 | hint: HINT
94 | }],
95 | r#"import foo from "npm:foo";"#: [{
96 | col: 16,
97 | message: MESSAGE,
98 | hint: HINT
99 | }],
100 | r#"import foo from "http://example.com/foo";"#: [{
101 | col: 16,
102 | message: MESSAGE,
103 | hint: HINT
104 | }],
105 | r#"import foo from "https://example.com/foo";"#: [{
106 | col: 16,
107 | message: MESSAGE,
108 | hint: HINT
109 | }],
110 | r#"import("jsr:@foo/foo");"#: [{
111 | col: 7,
112 | message: MESSAGE,
113 | hint: HINT
114 | }],
115 | r#"import("npm:foo");"#: [{
116 | col: 7,
117 | message: MESSAGE,
118 | hint: HINT
119 | }],
120 | r#"import("http://example.com/foo");"#: [{
121 | col: 7,
122 | message: MESSAGE,
123 | hint: HINT
124 | }],
125 | r#"import("https://example.com/foo");"#: [{
126 | col: 7,
127 | message: MESSAGE,
128 | hint: HINT
129 | }],
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/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/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/triple_slash_reference.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2 |
3 | use super::{Context, LintRule};
4 |
5 | use deno_ast::swc::common::comments::Comment;
6 | use deno_ast::swc::common::comments::CommentKind;
7 | use deno_ast::SourceRange;
8 | use deno_ast::SourceRangedForSpanned;
9 | use derive_more::Display;
10 | use once_cell::sync::Lazy;
11 | use regex::Regex;
12 |
13 | #[derive(Debug)]
14 | pub struct TripleSlashReference;
15 |
16 | const CODE: &str = "triple-slash-reference";
17 |
18 | #[derive(Display)]
19 | enum TripleSlashReferenceMessage {
20 | #[display(fmt = "`triple slash reference` is not allowed")]
21 | Unexpected,
22 | }
23 |
24 | impl TripleSlashReference {
25 | fn report(&self, context: &mut Context, range: SourceRange) {
26 | context.add_diagnostic(
27 | range,
28 | CODE,
29 | TripleSlashReferenceMessage::Unexpected,
30 | );
31 | }
32 | }
33 |
34 | impl LintRule for TripleSlashReference {
35 | fn code(&self) -> &'static str {
36 | CODE
37 | }
38 |
39 | fn lint_program_with_ast_view<'view>(
40 | &self,
41 | context: &mut Context<'view>,
42 | _program: deno_ast::view::Program<'view>,
43 | ) {
44 | let mut violated_comment_ranges = Vec::new();
45 |
46 | violated_comment_ranges.extend(context.all_comments().filter_map(|c| {
47 | if check_comment(c) {
48 | Some(c.range())
49 | } else {
50 | None
51 | }
52 | }));
53 |
54 | for range in violated_comment_ranges {
55 | self.report(context, range);
56 | }
57 | }
58 | }
59 |
60 | /// Returns `true` if the comment should be reported.
61 | fn check_comment(comment: &Comment) -> bool {
62 | if comment.kind != CommentKind::Line {
63 | return false;
64 | }
65 |
66 | static TSR_REGEX: Lazy = Lazy::new(|| {
67 | Regex::new(r#"^/\s*
84 | //
85 | //
86 | import * as foo from 'foo';
87 | import * as bar from 'bar';
88 | import * as baz from 'baz';
89 | "#,
90 | r#"
91 | //
92 | //
93 | //
94 | import foo = require('foo');
95 | import bar = require('bar');
96 | import baz = require('baz');"#,
97 | r#"
98 | /*
99 | ///
100 | */
101 | import * as foo from 'foo';"#,
102 | };
103 | }
104 |
105 | #[test]
106 | fn triple_slash_reference_invalid() {
107 | assert_lint_err! {
108 | TripleSlashReference,
109 | r#"
110 | ///
111 | import * as foo from 'foo';"#:[
112 | {
113 | line: 2,
114 | col: 0,
115 | message: TripleSlashReferenceMessage::Unexpected,
116 | }],
117 | r#"
118 | ///
119 | import foo = require('foo');
120 | "#:[
121 | {
122 | line: 2,
123 | col: 0,
124 | message: TripleSlashReferenceMessage::Unexpected,
125 | }],
126 | r#"/// "#: [
127 | {
128 | col: 0,
129 | message: TripleSlashReferenceMessage::Unexpected,
130 | }],
131 | r#"/// "#: [
132 | {
133 | col: 0,
134 | message: TripleSlashReferenceMessage::Unexpected,
135 | }],
136 | r#"/// "#: [
137 | {
138 | col: 0,
139 | message: TripleSlashReferenceMessage::Unexpected,
140 | }],
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/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/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/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/prefer_ascii.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::SourceRange;
6 |
7 | #[derive(Debug)]
8 | pub struct PreferAscii;
9 |
10 | const CODE: &str = "prefer-ascii";
11 | const MESSAGE: &str = "Non-ASCII characters are not allowed";
12 |
13 | fn hint(c: char) -> String {
14 | format!(
15 | "`{}` is \\u{{{:04x}}} and this is not an ASCII. Consider replacing it with an ASCII character",
16 | c, c as u32
17 | )
18 | }
19 |
20 | impl LintRule for PreferAscii {
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 | let mut not_asciis = Vec::new();
31 |
32 | let text_info = context.text_info();
33 | let mut src_chars = text_info.text_str().char_indices().peekable();
34 | let start_pos = text_info.range().start;
35 | while let Some((i, c)) = src_chars.next() {
36 | if let Some(&(pi, _)) = src_chars.peek() {
37 | if (pi > i + 1) || !c.is_ascii() {
38 | let range = SourceRange::new(start_pos + i, start_pos + pi);
39 | not_asciis.push((c, range));
40 | }
41 | }
42 | }
43 |
44 | for (c, range) in not_asciis {
45 | context.add_diagnostic_with_hint(range, CODE, MESSAGE, hint(c));
46 | }
47 | }
48 | }
49 |
50 | #[cfg(test)]
51 | mod tests {
52 | use super::*;
53 |
54 | #[test]
55 | fn prefer_ascii_valid() {
56 | assert_lint_ok! {
57 | PreferAscii,
58 | r#"const pi = Math.PI;"#,
59 | r#"const ninja = "ninja";"#,
60 | r#"
61 | function hello(name: string) {
62 | console.log(`Hello, ${name}`);
63 | }
64 | "#,
65 | r#"// "comments" are also checked"#,
66 | r#"/* "comments" are also checked */"#,
67 | };
68 | }
69 |
70 | #[test]
71 | fn prefer_ascii_invalid() {
72 | assert_lint_err! {
73 | PreferAscii,
74 | r#"const π = Math.PI;"#: [
75 | {
76 | line: 1,
77 | col: 6,
78 | message: MESSAGE,
79 | hint: hint('π'),
80 | },
81 | ],
82 | r#"const ninja = "🥷";"#: [
83 | {
84 | line: 1,
85 | col: 15,
86 | message: MESSAGE,
87 | hint: hint('🥷'),
88 | },
89 | ],
90 | r#"function こんにちは(名前: string) {}"#: [
91 | {
92 | line: 1,
93 | col: 9,
94 | message: MESSAGE,
95 | hint: hint('こ'),
96 | },
97 | {
98 | line: 1,
99 | col: 10,
100 | message: MESSAGE,
101 | hint: hint('ん'),
102 | },
103 | {
104 | line: 1,
105 | col: 11,
106 | message: MESSAGE,
107 | hint: hint('に'),
108 | },
109 | {
110 | line: 1,
111 | col: 12,
112 | message: MESSAGE,
113 | hint: hint('ち'),
114 | },
115 | {
116 | line: 1,
117 | col: 13,
118 | message: MESSAGE,
119 | hint: hint('は'),
120 | },
121 | {
122 | line: 1,
123 | col: 15,
124 | message: MESSAGE,
125 | hint: hint('名'),
126 | },
127 | {
128 | line: 1,
129 | col: 16,
130 | message: MESSAGE,
131 | hint: hint('前'),
132 | },
133 | ],
134 | r#"// “comments” are also checked"#: [
135 | {
136 | line: 1,
137 | col: 3,
138 | message: MESSAGE,
139 | hint: hint('“'),
140 | },
141 | {
142 | line: 1,
143 | col: 12,
144 | message: MESSAGE,
145 | hint: hint('”'),
146 | },
147 | ],
148 | r#"/* “comments” are also checked */"#: [
149 | {
150 | line: 1,
151 | col: 3,
152 | message: MESSAGE,
153 | hint: hint('“'),
154 | },
155 | {
156 | line: 1,
157 | col: 12,
158 | message: MESSAGE,
159 | hint: hint('”'),
160 | },
161 | ],
162 | };
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/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_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_window.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2020-2021 the Deno authors. All rights reserved. MIT license.
2 | use super::Context;
3 | use super::LintRule;
4 | use crate::diagnostic::LintFix;
5 | use crate::diagnostic::LintFixChange;
6 | use crate::handler::Handler;
7 | use crate::handler::Traverse;
8 | use crate::tags;
9 | use crate::tags::Tags;
10 | use crate::Program;
11 |
12 | use deno_ast::view as ast_view;
13 | use deno_ast::SourceRange;
14 | use deno_ast::SourceRanged;
15 | use if_chain::if_chain;
16 |
17 | #[derive(Debug)]
18 | pub struct NoWindow;
19 |
20 | const CODE: &str = "no-window";
21 | const MESSAGE: &str = "Window is no longer available in Deno";
22 | const HINT: &str = "Instead, use `globalThis`";
23 | const FIX_DESC: &str = "Rename window to globalThis";
24 |
25 | impl LintRule for NoWindow {
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 | NoWindowGlobalHandler.traverse(program, context);
40 | }
41 | }
42 |
43 | struct NoWindowGlobalHandler;
44 |
45 | impl NoWindowGlobalHandler {
46 | fn add_diagnostic(&self, ctx: &mut Context, range: SourceRange) {
47 | ctx.add_diagnostic_with_fixes(
48 | range,
49 | CODE,
50 | MESSAGE,
51 | Some(HINT.to_string()),
52 | vec![LintFix {
53 | description: FIX_DESC.into(),
54 | changes: vec![LintFixChange {
55 | new_text: "globalThis".into(),
56 | range,
57 | }],
58 | }],
59 | );
60 | }
61 | }
62 |
63 | impl Handler for NoWindowGlobalHandler {
64 | fn member_expr(&mut self, expr: &ast_view::MemberExpr, ctx: &mut Context) {
65 | use deno_ast::view::Expr;
66 | if_chain! {
67 | if let Expr::Ident(ident) = &expr.obj;
68 | if ident.sym() == "window";
69 | if ctx.scope().is_global(&ident.inner.to_id());
70 | then {
71 | self.add_diagnostic(ctx, ident.range());
72 | }
73 | }
74 | }
75 |
76 | fn expr_stmt(&mut self, expr: &ast_view::ExprStmt, ctx: &mut Context) {
77 | use deno_ast::view::Expr;
78 | if_chain! {
79 | if let Expr::Ident(ident) = &expr.expr;
80 | if ident.sym() == "window";
81 | if ctx.scope().is_global(&ident.inner.to_id());
82 | then {
83 | self.add_diagnostic(ctx, ident.range());
84 | }
85 | }
86 | }
87 | }
88 |
89 | #[cfg(test)]
90 | mod tests {
91 | use super::*;
92 |
93 | #[test]
94 | fn no_window_valid() {
95 | assert_lint_ok! {
96 | NoWindow,
97 | "fetch();",
98 | "self.fetch();",
99 | "globalThis.fetch();",
100 |
101 | // `window` is shadowed
102 | "const window = 42; window.fetch();",
103 | r#"const window = 42; window["fetch"]();"#,
104 | r#"const window = 42; window[`fetch`]();"#,
105 | "const window = 42; window.alert();",
106 | r#"const window = 42; window["alert"]();"#,
107 | r#"const window = 42; window[`alert`]();"#,
108 |
109 | // https://github.com/denoland/deno_lint/issues/1232
110 | "const params: { window: number } = { window: 23 };",
111 | "x.window"
112 | };
113 | }
114 |
115 | #[test]
116 | fn no_window_invalid() {
117 | assert_lint_err! {
118 | NoWindow,
119 | MESSAGE,
120 | HINT,
121 | r#"window.fetch()"#: [
122 | {
123 | col: 0,
124 | fix: (FIX_DESC, "globalThis.fetch()"),
125 | }
126 | ],
127 | r#"window["fetch"]()"#: [
128 | {
129 | col: 0,
130 | fix: (FIX_DESC, r#"globalThis["fetch"]()"#),
131 | }
132 | ],
133 | r#"window[`fetch`]()"#: [
134 | {
135 | col: 0,
136 | fix: (FIX_DESC, "globalThis[`fetch`]()"),
137 | }
138 | ],
139 | r#"
140 | function foo() {
141 | const window = 42;
142 | return window;
143 | }
144 | window;"#: [
145 | {
146 | col: 0,
147 | line: 6,
148 | fix: (FIX_DESC, "
149 | function foo() {
150 | const window = 42;
151 | return window;
152 | }
153 | globalThis;"),
154 | }
155 | ],
156 | r#"window.console.log()"#: [
157 | {
158 | col: 0,
159 | fix: (FIX_DESC, "globalThis.console.log()"),
160 | }
161 | ],
162 | };
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/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_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