mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
feat(lint): add require-node-prefix
rule
This commit is contained in:
parent
19cb0d949a
commit
3ff0b178e0
3 changed files with 277 additions and 6 deletions
|
@ -18,6 +18,7 @@ use crate::resolver::CliSloppyImportsResolver;
|
||||||
|
|
||||||
mod no_sloppy_imports;
|
mod no_sloppy_imports;
|
||||||
mod no_slow_types;
|
mod no_slow_types;
|
||||||
|
mod require_node_prefix;
|
||||||
|
|
||||||
// used for publishing
|
// used for publishing
|
||||||
pub use no_slow_types::collect_no_slow_type_diagnostics;
|
pub use no_slow_types::collect_no_slow_type_diagnostics;
|
||||||
|
@ -177,12 +178,17 @@ impl LintRuleProvider {
|
||||||
maybe_config_file: Option<&ConfigFile>,
|
maybe_config_file: Option<&ConfigFile>,
|
||||||
) -> ConfiguredRules {
|
) -> ConfiguredRules {
|
||||||
let deno_lint_rules = deno_lint::rules::get_all_rules();
|
let deno_lint_rules = deno_lint::rules::get_all_rules();
|
||||||
let cli_lint_rules = vec![CliLintRule(CliLintRuleKind::Extended(
|
let cli_lint_rules = vec![
|
||||||
Box::new(no_sloppy_imports::NoSloppyImportsRule::new(
|
CliLintRule(CliLintRuleKind::Extended(Box::new(
|
||||||
|
no_sloppy_imports::NoSloppyImportsRule::new(
|
||||||
self.sloppy_imports_resolver.clone(),
|
self.sloppy_imports_resolver.clone(),
|
||||||
self.workspace_resolver.clone(),
|
self.workspace_resolver.clone(),
|
||||||
)),
|
),
|
||||||
))];
|
))),
|
||||||
|
CliLintRule(CliLintRuleKind::Extended(Box::new(
|
||||||
|
require_node_prefix::RequireNodePrefix::new(),
|
||||||
|
))),
|
||||||
|
];
|
||||||
let cli_graph_rules = vec![CliLintRule(CliLintRuleKind::Package(
|
let cli_graph_rules = vec![CliLintRule(CliLintRuleKind::Package(
|
||||||
Box::new(no_slow_types::NoSlowTypesRule),
|
Box::new(no_slow_types::NoSlowTypesRule),
|
||||||
))];
|
))];
|
||||||
|
|
19
cli/tools/lint/rules/require_node_prefix.md
Normal file
19
cli/tools/lint/rules/require_node_prefix.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
Enforces the use of the `node:` specifier for Node built-in modules.
|
||||||
|
|
||||||
|
Deno requires Node built-in modules to be imported with the `node:` specifier.
|
||||||
|
|
||||||
|
### Invalid:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import * as path from "path";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as fsPromises from "fs/promises";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Valid:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import * as path from "node:path";
|
||||||
|
import * as fs from "node:fs";
|
||||||
|
import * as fsPromises from "node:fs/promises";
|
||||||
|
```
|
246
cli/tools/lint/rules/require_node_prefix.rs
Normal file
246
cli/tools/lint/rules/require_node_prefix.rs
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
|
use super::ExtendedLintRule;
|
||||||
|
|
||||||
|
use deno_ast::view as ast_view;
|
||||||
|
use deno_ast::SourceRange;
|
||||||
|
use deno_ast::SourceRanged;
|
||||||
|
use deno_lint::context::Context;
|
||||||
|
use deno_lint::diagnostic::LintFix;
|
||||||
|
use deno_lint::diagnostic::LintFixChange;
|
||||||
|
use deno_lint::rules::LintRule;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RequireNodePrefix;
|
||||||
|
|
||||||
|
const CODE: &str = "require-node-prefix";
|
||||||
|
const MESSAGE: &str = "built-in Node modules require the \"node:\" specifier";
|
||||||
|
const HINT: &str = "Add \"node:\" prefix in front of the import specifier";
|
||||||
|
const FIX_DESC: &str = "Add \"node:\" prefix";
|
||||||
|
const DOCS_URL: &str =
|
||||||
|
"https://docs.deno.com/runtime/manual/node/migrate/#node.js-built-ins";
|
||||||
|
|
||||||
|
impl ExtendedLintRule for RequireNodePrefix {
|
||||||
|
fn supports_incremental_cache(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn help_docs_url(&self) -> std::borrow::Cow<'static, str> {
|
||||||
|
Cow::Borrowed(DOCS_URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_base(self: Box<Self>) -> Box<dyn deno_lint::rules::LintRule> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LintRule for RequireNodePrefix {
|
||||||
|
fn tags(&self) -> &'static [&'static str] {
|
||||||
|
&["recommended"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code(&self) -> &'static str {
|
||||||
|
CODE
|
||||||
|
}
|
||||||
|
|
||||||
|
fn docs(&self) -> &'static str {
|
||||||
|
include_str!("require_node_prefix.md")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lint_program_with_ast_view<'view>(
|
||||||
|
&self,
|
||||||
|
context: &mut Context<'view>,
|
||||||
|
program: deno_lint::Program<'view>,
|
||||||
|
) {
|
||||||
|
NodeBuiltinsSpecifierGlobalHandler.traverse(program, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NodeBuiltinsSpecifierGlobalHandler;
|
||||||
|
|
||||||
|
impl NodeBuiltinsSpecifierGlobalHandler {
|
||||||
|
fn add_diagnostic(&self, ctx: &mut Context, src: &str, range: SourceRange) {
|
||||||
|
let specifier = format!(r#""node:{}""#, src);
|
||||||
|
|
||||||
|
ctx.add_diagnostic_with_fixes(
|
||||||
|
range,
|
||||||
|
CODE,
|
||||||
|
MESSAGE,
|
||||||
|
Some(HINT.to_string()),
|
||||||
|
vec![LintFix {
|
||||||
|
description: FIX_DESC.into(),
|
||||||
|
changes: vec![LintFixChange {
|
||||||
|
new_text: specifier.into(),
|
||||||
|
range,
|
||||||
|
}],
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler for NodeBuiltinsSpecifierGlobalHandler {
|
||||||
|
fn import_decl(&mut self, decl: &ast_view::ImportDecl, ctx: &mut Context) {
|
||||||
|
let src = decl.src.inner.value.as_str();
|
||||||
|
if is_bare_node_builtin(src) {
|
||||||
|
self.add_diagnostic(ctx, src, decl.src.range());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_expr(&mut self, expr: &ast_view::CallExpr, ctx: &mut Context) {
|
||||||
|
if let ast_view::Callee::Import(_) = expr.callee {
|
||||||
|
if let Some(src_expr) = expr.args.first() {
|
||||||
|
if let ast_view::Expr::Lit(lit) = src_expr.expr {
|
||||||
|
if let ast_view::Lit::Str(str_value) = lit {
|
||||||
|
let src = str_value.inner.value.as_str();
|
||||||
|
if is_bare_node_builtin(src) {
|
||||||
|
self.add_diagnostic(ctx, src, lit.range());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should match https://nodejs.org/api/module.html#modulebuiltinmodules
|
||||||
|
fn is_bare_node_builtin(src: &str) -> bool {
|
||||||
|
matches!(
|
||||||
|
src,
|
||||||
|
"assert"
|
||||||
|
| "assert/strict"
|
||||||
|
| "async_hooks"
|
||||||
|
| "buffer"
|
||||||
|
| "child_process"
|
||||||
|
| "cluster"
|
||||||
|
| "console"
|
||||||
|
| "constants"
|
||||||
|
| "crypto"
|
||||||
|
| "dgram"
|
||||||
|
| "diagnostics_channel"
|
||||||
|
| "dns"
|
||||||
|
| "dns/promises"
|
||||||
|
| "domain"
|
||||||
|
| "events"
|
||||||
|
| "fs"
|
||||||
|
| "fs/promises"
|
||||||
|
| "http"
|
||||||
|
| "http2"
|
||||||
|
| "https"
|
||||||
|
| "inspector"
|
||||||
|
| "inspector/promises"
|
||||||
|
| "module"
|
||||||
|
| "net"
|
||||||
|
| "os"
|
||||||
|
| "path"
|
||||||
|
| "path/posix"
|
||||||
|
| "path/win32"
|
||||||
|
| "perf_hooks"
|
||||||
|
| "process"
|
||||||
|
| "punycode"
|
||||||
|
| "querystring"
|
||||||
|
| "readline"
|
||||||
|
| "readline/promises"
|
||||||
|
| "repl"
|
||||||
|
| "stream"
|
||||||
|
| "stream/consumers"
|
||||||
|
| "stream/promises"
|
||||||
|
| "stream/web"
|
||||||
|
| "string_decoder"
|
||||||
|
| "sys"
|
||||||
|
| "timers"
|
||||||
|
| "timers/promises"
|
||||||
|
| "tls"
|
||||||
|
| "trace_events"
|
||||||
|
| "tty"
|
||||||
|
| "url"
|
||||||
|
| "util"
|
||||||
|
| "util/types"
|
||||||
|
| "v8"
|
||||||
|
| "vm"
|
||||||
|
| "wasi"
|
||||||
|
| "worker_threads"
|
||||||
|
| "zlib"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn node_specifier_valid() {
|
||||||
|
assert_lint_ok! {
|
||||||
|
NodeBuiltinsSpecifier,
|
||||||
|
r#"import "node:path";"#,
|
||||||
|
r#"import "node:fs";"#,
|
||||||
|
r#"import "node:fs/promises";"#,
|
||||||
|
|
||||||
|
r#"import * as fs from "node:fs";"#,
|
||||||
|
r#"import * as fsPromises from "node:fs/promises";"#,
|
||||||
|
r#"import fsPromises from "node:fs/promises";"#,
|
||||||
|
|
||||||
|
r#"await import("node:fs");"#,
|
||||||
|
r#"await import("node:fs/promises");"#,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn node_specifier_invalid() {
|
||||||
|
assert_lint_err! {
|
||||||
|
NodeBuiltinsSpecifier,
|
||||||
|
MESSAGE,
|
||||||
|
HINT,
|
||||||
|
r#"import "path";"#: [
|
||||||
|
{
|
||||||
|
col: 7,
|
||||||
|
fix: (FIX_DESC, r#"import "node:path";"#),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
r#"import "fs";"#: [
|
||||||
|
{
|
||||||
|
col: 7,
|
||||||
|
fix: (FIX_DESC, r#"import "node:fs";"#),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
r#"import "fs/promises";"#: [
|
||||||
|
{
|
||||||
|
col: 7,
|
||||||
|
fix: (FIX_DESC, r#"import "node:fs/promises";"#),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
r#"import * as fs from "fs";"#: [
|
||||||
|
{
|
||||||
|
col: 20,
|
||||||
|
fix: (FIX_DESC, r#"import * as fs from "node:fs";"#),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
r#"import * as fsPromises from "fs/promises";"#: [
|
||||||
|
{
|
||||||
|
col: 28,
|
||||||
|
fix: (FIX_DESC, r#"import * as fsPromises from "node:fs/promises";"#),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
r#"import fsPromises from "fs/promises";"#: [
|
||||||
|
{
|
||||||
|
col: 23,
|
||||||
|
fix: (FIX_DESC, r#"import fsPromises from "node:fs/promises";"#),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
r#"await import("fs");"#: [
|
||||||
|
{
|
||||||
|
col: 13,
|
||||||
|
fix: (FIX_DESC, r#"await import("node:fs");"#),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
r#"await import("fs/promises");"#: [
|
||||||
|
{
|
||||||
|
col: 13,
|
||||||
|
fix: (FIX_DESC, r#"await import("node:fs/promises");"#),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue