mirror of
https://github.com/denoland/deno.git
synced 2024-11-24 15:19:26 -05:00
feat(lint): deno lint --fix
and lsp quick fixes (#22615)
Adds a `--fix` option to deno lint. This currently doesn't work for basically any rules, but we can add them over time to deno lint.
This commit is contained in:
parent
2166aa8fb6
commit
ffbcad3800
20 changed files with 442 additions and 59 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -1554,9 +1554,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deno_lint"
|
name = "deno_lint"
|
||||||
version = "0.57.1"
|
version = "0.58.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a96b203021bf8c738b37e6d3e792e9b04ed61ed4b9204426bf29ed637ccb9ed0"
|
checksum = "3344701fc527a920b4f00562052d3feca8ce315bb9327305b07d5820019a9ead"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"deno_ast",
|
"deno_ast",
|
||||||
|
|
|
@ -69,7 +69,7 @@ deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"]
|
||||||
deno_doc = { version = "=0.113.1", features = ["html"] }
|
deno_doc = { version = "=0.113.1", features = ["html"] }
|
||||||
deno_emit = "=0.38.2"
|
deno_emit = "=0.38.2"
|
||||||
deno_graph = { version = "=0.69.9", features = ["tokio_executor"] }
|
deno_graph = { version = "=0.69.9", features = ["tokio_executor"] }
|
||||||
deno_lint = { version = "=0.57.1", features = ["docs"] }
|
deno_lint = { version = "=0.58.0", features = ["docs"] }
|
||||||
deno_lockfile.workspace = true
|
deno_lockfile.workspace = true
|
||||||
deno_npm = "=0.17.0"
|
deno_npm = "=0.17.0"
|
||||||
deno_runtime = { workspace = true, features = ["include_js_files_for_snapshotting"] }
|
deno_runtime = { workspace = true, features = ["include_js_files_for_snapshotting"] }
|
||||||
|
|
|
@ -199,6 +199,7 @@ pub struct UninstallFlags {
|
||||||
pub struct LintFlags {
|
pub struct LintFlags {
|
||||||
pub files: FileFlags,
|
pub files: FileFlags,
|
||||||
pub rules: bool,
|
pub rules: bool,
|
||||||
|
pub fix: bool,
|
||||||
pub maybe_rules_tags: Option<Vec<String>>,
|
pub maybe_rules_tags: Option<Vec<String>>,
|
||||||
pub maybe_rules_include: Option<Vec<String>>,
|
pub maybe_rules_include: Option<Vec<String>>,
|
||||||
pub maybe_rules_exclude: Option<Vec<String>>,
|
pub maybe_rules_exclude: Option<Vec<String>>,
|
||||||
|
@ -2005,6 +2006,12 @@ Ignore linting a file by adding an ignore comment at the top of the file:
|
||||||
)
|
)
|
||||||
.defer(|cmd| {
|
.defer(|cmd| {
|
||||||
cmd
|
cmd
|
||||||
|
.arg(
|
||||||
|
Arg::new("fix")
|
||||||
|
.long("fix")
|
||||||
|
.help("Fix any linting errors for rules that support it")
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("rules")
|
Arg::new("rules")
|
||||||
.long("rules")
|
.long("rules")
|
||||||
|
@ -3622,6 +3629,7 @@ fn lint_parse(flags: &mut Flags, matches: &mut ArgMatches) {
|
||||||
Some(f) => f.collect(),
|
Some(f) => f.collect(),
|
||||||
None => vec![],
|
None => vec![],
|
||||||
};
|
};
|
||||||
|
let fix = matches.get_flag("fix");
|
||||||
let rules = matches.get_flag("rules");
|
let rules = matches.get_flag("rules");
|
||||||
let maybe_rules_tags = matches
|
let maybe_rules_tags = matches
|
||||||
.remove_many::<String>("rules-tags")
|
.remove_many::<String>("rules-tags")
|
||||||
|
@ -3642,6 +3650,7 @@ fn lint_parse(flags: &mut Flags, matches: &mut ArgMatches) {
|
||||||
include: files,
|
include: files,
|
||||||
ignore,
|
ignore,
|
||||||
},
|
},
|
||||||
|
fix,
|
||||||
rules,
|
rules,
|
||||||
maybe_rules_tags,
|
maybe_rules_tags,
|
||||||
maybe_rules_include,
|
maybe_rules_include,
|
||||||
|
@ -5015,6 +5024,7 @@ mod tests {
|
||||||
include: vec!["script_1.ts".to_string(), "script_2.ts".to_string(),],
|
include: vec!["script_1.ts".to_string(), "script_2.ts".to_string(),],
|
||||||
ignore: vec![],
|
ignore: vec![],
|
||||||
},
|
},
|
||||||
|
fix: false,
|
||||||
rules: false,
|
rules: false,
|
||||||
maybe_rules_tags: None,
|
maybe_rules_tags: None,
|
||||||
maybe_rules_include: None,
|
maybe_rules_include: None,
|
||||||
|
@ -5042,6 +5052,7 @@ mod tests {
|
||||||
include: vec!["script_1.ts".to_string(), "script_2.ts".to_string()],
|
include: vec!["script_1.ts".to_string(), "script_2.ts".to_string()],
|
||||||
ignore: vec![],
|
ignore: vec![],
|
||||||
},
|
},
|
||||||
|
fix: false,
|
||||||
rules: false,
|
rules: false,
|
||||||
maybe_rules_tags: None,
|
maybe_rules_tags: None,
|
||||||
maybe_rules_include: None,
|
maybe_rules_include: None,
|
||||||
|
@ -5070,6 +5081,7 @@ mod tests {
|
||||||
include: vec!["script_1.ts".to_string(), "script_2.ts".to_string()],
|
include: vec!["script_1.ts".to_string(), "script_2.ts".to_string()],
|
||||||
ignore: vec![],
|
ignore: vec![],
|
||||||
},
|
},
|
||||||
|
fix: false,
|
||||||
rules: false,
|
rules: false,
|
||||||
maybe_rules_tags: None,
|
maybe_rules_tags: None,
|
||||||
maybe_rules_include: None,
|
maybe_rules_include: None,
|
||||||
|
@ -5085,8 +5097,12 @@ mod tests {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let r =
|
let r = flags_from_vec(svec![
|
||||||
flags_from_vec(svec!["deno", "lint", "--ignore=script_1.ts,script_2.ts"]);
|
"deno",
|
||||||
|
"lint",
|
||||||
|
"--fix",
|
||||||
|
"--ignore=script_1.ts,script_2.ts"
|
||||||
|
]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
r.unwrap(),
|
r.unwrap(),
|
||||||
Flags {
|
Flags {
|
||||||
|
@ -5095,6 +5111,7 @@ mod tests {
|
||||||
include: vec![],
|
include: vec![],
|
||||||
ignore: vec!["script_1.ts".to_string(), "script_2.ts".to_string()],
|
ignore: vec!["script_1.ts".to_string(), "script_2.ts".to_string()],
|
||||||
},
|
},
|
||||||
|
fix: true,
|
||||||
rules: false,
|
rules: false,
|
||||||
maybe_rules_tags: None,
|
maybe_rules_tags: None,
|
||||||
maybe_rules_include: None,
|
maybe_rules_include: None,
|
||||||
|
@ -5116,6 +5133,7 @@ mod tests {
|
||||||
include: vec![],
|
include: vec![],
|
||||||
ignore: vec![],
|
ignore: vec![],
|
||||||
},
|
},
|
||||||
|
fix: false,
|
||||||
rules: true,
|
rules: true,
|
||||||
maybe_rules_tags: None,
|
maybe_rules_tags: None,
|
||||||
maybe_rules_include: None,
|
maybe_rules_include: None,
|
||||||
|
@ -5142,6 +5160,7 @@ mod tests {
|
||||||
include: vec![],
|
include: vec![],
|
||||||
ignore: vec![],
|
ignore: vec![],
|
||||||
},
|
},
|
||||||
|
fix: false,
|
||||||
rules: true,
|
rules: true,
|
||||||
maybe_rules_tags: Some(svec!["recommended"]),
|
maybe_rules_tags: Some(svec!["recommended"]),
|
||||||
maybe_rules_include: None,
|
maybe_rules_include: None,
|
||||||
|
@ -5169,6 +5188,7 @@ mod tests {
|
||||||
include: vec![],
|
include: vec![],
|
||||||
ignore: vec![],
|
ignore: vec![],
|
||||||
},
|
},
|
||||||
|
fix: false,
|
||||||
rules: false,
|
rules: false,
|
||||||
maybe_rules_tags: Some(svec![""]),
|
maybe_rules_tags: Some(svec![""]),
|
||||||
maybe_rules_include: Some(svec!["ban-untagged-todo", "no-undef"]),
|
maybe_rules_include: Some(svec!["ban-untagged-todo", "no-undef"]),
|
||||||
|
@ -5190,6 +5210,7 @@ mod tests {
|
||||||
include: vec!["script_1.ts".to_string()],
|
include: vec!["script_1.ts".to_string()],
|
||||||
ignore: vec![],
|
ignore: vec![],
|
||||||
},
|
},
|
||||||
|
fix: false,
|
||||||
rules: false,
|
rules: false,
|
||||||
maybe_rules_tags: None,
|
maybe_rules_tags: None,
|
||||||
maybe_rules_include: None,
|
maybe_rules_include: None,
|
||||||
|
@ -5218,6 +5239,7 @@ mod tests {
|
||||||
include: vec!["script_1.ts".to_string()],
|
include: vec!["script_1.ts".to_string()],
|
||||||
ignore: vec![],
|
ignore: vec![],
|
||||||
},
|
},
|
||||||
|
fix: false,
|
||||||
rules: false,
|
rules: false,
|
||||||
maybe_rules_tags: None,
|
maybe_rules_tags: None,
|
||||||
maybe_rules_include: None,
|
maybe_rules_include: None,
|
||||||
|
@ -5247,6 +5269,7 @@ mod tests {
|
||||||
include: vec!["script_1.ts".to_string()],
|
include: vec!["script_1.ts".to_string()],
|
||||||
ignore: vec![],
|
ignore: vec![],
|
||||||
},
|
},
|
||||||
|
fix: false,
|
||||||
rules: false,
|
rules: false,
|
||||||
maybe_rules_tags: None,
|
maybe_rules_tags: None,
|
||||||
maybe_rules_include: None,
|
maybe_rules_include: None,
|
||||||
|
|
|
@ -391,6 +391,7 @@ pub struct LintOptions {
|
||||||
pub rules: LintRulesConfig,
|
pub rules: LintRulesConfig,
|
||||||
pub files: FilePatterns,
|
pub files: FilePatterns,
|
||||||
pub reporter_kind: LintReporterKind,
|
pub reporter_kind: LintReporterKind,
|
||||||
|
pub fix: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LintOptions {
|
impl LintOptions {
|
||||||
|
@ -399,6 +400,7 @@ impl LintOptions {
|
||||||
rules: Default::default(),
|
rules: Default::default(),
|
||||||
files: FilePatterns::new_with_base(base),
|
files: FilePatterns::new_with_base(base),
|
||||||
reporter_kind: Default::default(),
|
reporter_kind: Default::default(),
|
||||||
|
fix: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,6 +409,7 @@ impl LintOptions {
|
||||||
maybe_lint_flags: Option<LintFlags>,
|
maybe_lint_flags: Option<LintFlags>,
|
||||||
initial_cwd: &Path,
|
initial_cwd: &Path,
|
||||||
) -> Result<Self, AnyError> {
|
) -> Result<Self, AnyError> {
|
||||||
|
let fix = maybe_lint_flags.as_ref().map(|f| f.fix).unwrap_or(false);
|
||||||
let mut maybe_reporter_kind =
|
let mut maybe_reporter_kind =
|
||||||
maybe_lint_flags.as_ref().and_then(|lint_flags| {
|
maybe_lint_flags.as_ref().and_then(|lint_flags| {
|
||||||
if lint_flags.json {
|
if lint_flags.json {
|
||||||
|
@ -464,6 +467,7 @@ impl LintOptions {
|
||||||
maybe_rules_include,
|
maybe_rules_include,
|
||||||
maybe_rules_exclude,
|
maybe_rules_exclude,
|
||||||
),
|
),
|
||||||
|
fix,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ use deno_core::anyhow::anyhow;
|
||||||
use deno_core::error::custom_error;
|
use deno_core::error::custom_error;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::serde::Deserialize;
|
use deno_core::serde::Deserialize;
|
||||||
|
use deno_core::serde::Serialize;
|
||||||
use deno_core::serde_json;
|
use deno_core::serde_json;
|
||||||
use deno_core::serde_json::json;
|
use deno_core::serde_json::json;
|
||||||
use deno_core::ModuleSpecifier;
|
use deno_core::ModuleSpecifier;
|
||||||
|
@ -78,6 +79,19 @@ static IMPORT_SPECIFIER_RE: Lazy<Regex> =
|
||||||
|
|
||||||
const SUPPORTED_EXTENSIONS: &[&str] = &[".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
const SUPPORTED_EXTENSIONS: &[&str] = &[".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct DataQuickFixChange {
|
||||||
|
pub range: Range,
|
||||||
|
pub new_text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A quick fix that's stored in the diagnostic's data field.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct DataQuickFix {
|
||||||
|
pub description: String,
|
||||||
|
pub changes: Vec<DataQuickFixChange>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Category of self-generated diagnostic messages (those not coming from)
|
/// Category of self-generated diagnostic messages (those not coming from)
|
||||||
/// TypeScript.
|
/// TypeScript.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
@ -87,6 +101,7 @@ pub enum Category {
|
||||||
message: String,
|
message: String,
|
||||||
code: String,
|
code: String,
|
||||||
hint: Option<String>,
|
hint: Option<String>,
|
||||||
|
quick_fixes: Vec<DataQuickFix>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +119,7 @@ impl Reference {
|
||||||
message,
|
message,
|
||||||
code,
|
code,
|
||||||
hint,
|
hint,
|
||||||
|
quick_fixes,
|
||||||
} => lsp::Diagnostic {
|
} => lsp::Diagnostic {
|
||||||
range: self.range,
|
range: self.range,
|
||||||
severity: Some(lsp::DiagnosticSeverity::WARNING),
|
severity: Some(lsp::DiagnosticSeverity::WARNING),
|
||||||
|
@ -120,19 +136,26 @@ impl Reference {
|
||||||
},
|
},
|
||||||
related_information: None,
|
related_information: None,
|
||||||
tags: None, // we should tag unused code
|
tags: None, // we should tag unused code
|
||||||
data: None,
|
data: if quick_fixes.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
serde_json::to_value(quick_fixes).ok()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_lsp_range(diagnostic: &LintDiagnostic) -> Range {
|
fn as_lsp_range_from_diagnostic(diagnostic: &LintDiagnostic) -> Range {
|
||||||
let start_lc = diagnostic
|
as_lsp_range(diagnostic.range, &diagnostic.text_info)
|
||||||
.text_info
|
}
|
||||||
.line_and_column_index(diagnostic.range.start);
|
|
||||||
let end_lc = diagnostic
|
fn as_lsp_range(
|
||||||
.text_info
|
source_range: SourceRange,
|
||||||
.line_and_column_index(diagnostic.range.end);
|
text_info: &SourceTextInfo,
|
||||||
|
) -> Range {
|
||||||
|
let start_lc = text_info.line_and_column_index(source_range.start);
|
||||||
|
let end_lc = text_info.line_and_column_index(source_range.end);
|
||||||
Range {
|
Range {
|
||||||
start: Position {
|
start: Position {
|
||||||
line: start_lc.line_index as u32,
|
line: start_lc.line_index as u32,
|
||||||
|
@ -156,11 +179,26 @@ pub fn get_lint_references(
|
||||||
lint_diagnostics
|
lint_diagnostics
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|d| Reference {
|
.map(|d| Reference {
|
||||||
range: as_lsp_range(&d),
|
range: as_lsp_range_from_diagnostic(&d),
|
||||||
category: Category::Lint {
|
category: Category::Lint {
|
||||||
message: d.message,
|
message: d.message,
|
||||||
code: d.code,
|
code: d.code,
|
||||||
hint: d.hint,
|
hint: d.hint,
|
||||||
|
quick_fixes: d
|
||||||
|
.fixes
|
||||||
|
.into_iter()
|
||||||
|
.map(|f| DataQuickFix {
|
||||||
|
description: f.description.to_string(),
|
||||||
|
changes: f
|
||||||
|
.changes
|
||||||
|
.into_iter()
|
||||||
|
.map(|change| DataQuickFixChange {
|
||||||
|
range: as_lsp_range(change.range, &d.text_info),
|
||||||
|
new_text: change.new_text.to_string(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
@ -668,7 +706,57 @@ impl CodeActionCollection {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_deno_lint_ignore_action(
|
pub fn add_deno_lint_actions(
|
||||||
|
&mut self,
|
||||||
|
specifier: &ModuleSpecifier,
|
||||||
|
diagnostic: &lsp::Diagnostic,
|
||||||
|
maybe_text_info: Option<SourceTextInfo>,
|
||||||
|
maybe_parsed_source: Option<deno_ast::ParsedSource>,
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
|
if let Some(data_quick_fixes) = diagnostic
|
||||||
|
.data
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|d| serde_json::from_value::<Vec<DataQuickFix>>(d.clone()).ok())
|
||||||
|
{
|
||||||
|
for quick_fix in data_quick_fixes {
|
||||||
|
let mut changes = HashMap::new();
|
||||||
|
changes.insert(
|
||||||
|
specifier.clone(),
|
||||||
|
quick_fix
|
||||||
|
.changes
|
||||||
|
.into_iter()
|
||||||
|
.map(|change| lsp::TextEdit {
|
||||||
|
new_text: change.new_text.clone(),
|
||||||
|
range: change.range,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
let code_action = lsp::CodeAction {
|
||||||
|
title: quick_fix.description.to_string(),
|
||||||
|
kind: Some(lsp::CodeActionKind::QUICKFIX),
|
||||||
|
diagnostics: Some(vec![diagnostic.clone()]),
|
||||||
|
command: None,
|
||||||
|
is_preferred: None,
|
||||||
|
disabled: None,
|
||||||
|
data: None,
|
||||||
|
edit: Some(lsp::WorkspaceEdit {
|
||||||
|
changes: Some(changes),
|
||||||
|
change_annotations: None,
|
||||||
|
document_changes: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
self.actions.push(CodeActionKind::DenoLint(code_action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.add_deno_lint_ignore_action(
|
||||||
|
specifier,
|
||||||
|
diagnostic,
|
||||||
|
maybe_text_info,
|
||||||
|
maybe_parsed_source,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_deno_lint_ignore_action(
|
||||||
&mut self,
|
&mut self,
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
diagnostic: &lsp::Diagnostic,
|
diagnostic: &lsp::Diagnostic,
|
||||||
|
@ -1087,6 +1175,7 @@ mod tests {
|
||||||
message: "message1".to_string(),
|
message: "message1".to_string(),
|
||||||
code: "code1".to_string(),
|
code: "code1".to_string(),
|
||||||
hint: None,
|
hint: None,
|
||||||
|
quick_fixes: Vec::new(),
|
||||||
},
|
},
|
||||||
range,
|
range,
|
||||||
},
|
},
|
||||||
|
@ -1105,6 +1194,7 @@ mod tests {
|
||||||
message: "message2".to_string(),
|
message: "message2".to_string(),
|
||||||
code: "code2".to_string(),
|
code: "code2".to_string(),
|
||||||
hint: Some("hint2".to_string()),
|
hint: Some("hint2".to_string()),
|
||||||
|
quick_fixes: Vec::new(),
|
||||||
},
|
},
|
||||||
range,
|
range,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1691,6 +1691,7 @@ let c: number = "a";
|
||||||
rules: Default::default(),
|
rules: Default::default(),
|
||||||
files: FilePatterns::new_with_base(temp_dir.path().to_path_buf()),
|
files: FilePatterns::new_with_base(temp_dir.path().to_path_buf()),
|
||||||
reporter_kind: Default::default(),
|
reporter_kind: Default::default(),
|
||||||
|
fix: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// test enabled
|
// test enabled
|
||||||
|
|
|
@ -2195,7 +2195,7 @@ impl Inner {
|
||||||
})?
|
})?
|
||||||
}
|
}
|
||||||
Some("deno-lint") => code_actions
|
Some("deno-lint") => code_actions
|
||||||
.add_deno_lint_ignore_action(
|
.add_deno_lint_actions(
|
||||||
&specifier,
|
&specifier,
|
||||||
diagnostic,
|
diagnostic,
|
||||||
asset_or_doc.document().map(|d| d.text_info()),
|
asset_or_doc.document().map(|d| d.text_info()),
|
||||||
|
|
|
@ -10,6 +10,7 @@ use deno_ast::SourceRange;
|
||||||
use deno_ast::SourceTextInfo;
|
use deno_ast::SourceTextInfo;
|
||||||
use deno_config::glob::FilePatterns;
|
use deno_config::glob::FilePatterns;
|
||||||
use deno_core::anyhow::bail;
|
use deno_core::anyhow::bail;
|
||||||
|
use deno_core::anyhow::Context;
|
||||||
use deno_core::error::generic_error;
|
use deno_core::error::generic_error;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::parking_lot::Mutex;
|
use deno_core::parking_lot::Mutex;
|
||||||
|
@ -216,9 +217,10 @@ async fn lint_files(
|
||||||
|
|
||||||
futures.push({
|
futures.push({
|
||||||
let has_error = has_error.clone();
|
let has_error = has_error.clone();
|
||||||
let lint_rules = lint_rules.rules.clone();
|
let linter = create_linter(lint_rules.rules);
|
||||||
let reporter_lock = reporter_lock.clone();
|
let reporter_lock = reporter_lock.clone();
|
||||||
let incremental_cache = incremental_cache.clone();
|
let incremental_cache = incremental_cache.clone();
|
||||||
|
let fix = lint_options.fix;
|
||||||
deno_core::unsync::spawn(async move {
|
deno_core::unsync::spawn(async move {
|
||||||
run_parallelized(paths, {
|
run_parallelized(paths, {
|
||||||
move |file_path| {
|
move |file_path| {
|
||||||
|
@ -229,12 +231,15 @@ async fn lint_files(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let r = lint_file(&file_path, file_text, lint_rules);
|
let r = lint_file(&linter, &file_path, file_text, fix);
|
||||||
if let Ok((file_diagnostics, file_source)) = &r {
|
if let Ok((file_source, file_diagnostics)) = &r {
|
||||||
if file_diagnostics.is_empty() {
|
if file_diagnostics.is_empty() {
|
||||||
// update the incremental cache if there were no diagnostics
|
// update the incremental cache if there were no diagnostics
|
||||||
incremental_cache
|
incremental_cache.update_file(
|
||||||
.update_file(&file_path, file_source.text_info().text_str())
|
&file_path,
|
||||||
|
// ensure the returned text is used here as it may have been modified via --fix
|
||||||
|
file_source.text_info().text_str(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,22 +327,145 @@ pub fn create_linter(rules: Vec<&'static dyn LintRule>) -> Linter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lint_file(
|
fn lint_file(
|
||||||
|
linter: &Linter,
|
||||||
file_path: &Path,
|
file_path: &Path,
|
||||||
source_code: String,
|
source_code: String,
|
||||||
lint_rules: Vec<&'static dyn LintRule>,
|
fix: bool,
|
||||||
) -> Result<(Vec<LintDiagnostic>, ParsedSource), AnyError> {
|
) -> Result<(ParsedSource, Vec<LintDiagnostic>), AnyError> {
|
||||||
let specifier = specifier_from_file_path(file_path)?;
|
let specifier = specifier_from_file_path(file_path)?;
|
||||||
let media_type = MediaType::from_specifier(&specifier);
|
let media_type = MediaType::from_specifier(&specifier);
|
||||||
|
|
||||||
let linter = create_linter(lint_rules);
|
if fix {
|
||||||
|
lint_file_and_fix(linter, &specifier, media_type, source_code, file_path)
|
||||||
|
} else {
|
||||||
|
linter
|
||||||
|
.lint_file(LintFileOptions {
|
||||||
|
specifier,
|
||||||
|
media_type,
|
||||||
|
source_code,
|
||||||
|
})
|
||||||
|
.map_err(AnyError::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let (source, file_diagnostics) = linter.lint_file(LintFileOptions {
|
fn lint_file_and_fix(
|
||||||
specifier,
|
linter: &Linter,
|
||||||
|
specifier: &ModuleSpecifier,
|
||||||
|
media_type: MediaType,
|
||||||
|
source_code: String,
|
||||||
|
file_path: &Path,
|
||||||
|
) -> Result<(ParsedSource, Vec<LintDiagnostic>), deno_core::anyhow::Error> {
|
||||||
|
// initial lint
|
||||||
|
let (source, diagnostics) = linter.lint_file(LintFileOptions {
|
||||||
|
specifier: specifier.clone(),
|
||||||
media_type,
|
media_type,
|
||||||
source_code: source_code.clone(),
|
source_code,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok((file_diagnostics, source))
|
// Try applying fixes repeatedly until the file has none left or
|
||||||
|
// a maximum number of iterations is reached. This is necessary
|
||||||
|
// because lint fixes may overlap and so we can't always apply
|
||||||
|
// them in one pass.
|
||||||
|
let mut source = source;
|
||||||
|
let mut diagnostics = diagnostics;
|
||||||
|
let mut fix_iterations = 0;
|
||||||
|
loop {
|
||||||
|
let change = apply_lint_fixes_and_relint(
|
||||||
|
specifier,
|
||||||
|
media_type,
|
||||||
|
linter,
|
||||||
|
source.text_info(),
|
||||||
|
&diagnostics,
|
||||||
|
)?;
|
||||||
|
match change {
|
||||||
|
Some(change) => {
|
||||||
|
source = change.0;
|
||||||
|
diagnostics = change.1;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fix_iterations += 1;
|
||||||
|
if fix_iterations > 5 {
|
||||||
|
log::warn!(
|
||||||
|
concat!(
|
||||||
|
"Reached maximum number of fix iterations for '{}'. There's ",
|
||||||
|
"probably a bug in Deno. Please fix this file manually.",
|
||||||
|
),
|
||||||
|
specifier,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fix_iterations > 0 {
|
||||||
|
// everything looks good and the file still parses, so write it out
|
||||||
|
fs::write(file_path, source.text_info().text_str())
|
||||||
|
.context("Failed writing fix to file.")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((source, diagnostics))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_lint_fixes_and_relint(
|
||||||
|
specifier: &ModuleSpecifier,
|
||||||
|
media_type: MediaType,
|
||||||
|
linter: &Linter,
|
||||||
|
text_info: &SourceTextInfo,
|
||||||
|
diagnostics: &[LintDiagnostic],
|
||||||
|
) -> Result<Option<(ParsedSource, Vec<LintDiagnostic>)>, AnyError> {
|
||||||
|
let Some(new_text) = apply_lint_fixes(text_info, diagnostics) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
linter
|
||||||
|
.lint_file(LintFileOptions {
|
||||||
|
specifier: specifier.clone(),
|
||||||
|
source_code: new_text,
|
||||||
|
media_type,
|
||||||
|
})
|
||||||
|
.map(Some)
|
||||||
|
.context(
|
||||||
|
"An applied lint fix caused a syntax error. Please report this bug.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_lint_fixes(
|
||||||
|
text_info: &SourceTextInfo,
|
||||||
|
diagnostics: &[LintDiagnostic],
|
||||||
|
) -> Option<String> {
|
||||||
|
if diagnostics.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_start = text_info.range().start;
|
||||||
|
let mut quick_fixes = diagnostics
|
||||||
|
.iter()
|
||||||
|
// use the first quick fix
|
||||||
|
.filter_map(|d| d.fixes.first())
|
||||||
|
.flat_map(|fix| fix.changes.iter())
|
||||||
|
.map(|change| deno_ast::TextChange {
|
||||||
|
range: change.range.as_byte_range(file_start),
|
||||||
|
new_text: change.new_text.to_string(),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if quick_fixes.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// remove any overlapping text changes, we'll circle
|
||||||
|
// back for another pass to fix the remaining
|
||||||
|
quick_fixes.sort_by_key(|change| change.range.start);
|
||||||
|
for i in (1..quick_fixes.len()).rev() {
|
||||||
|
let cur = &quick_fixes[i];
|
||||||
|
let previous = &quick_fixes[i - 1];
|
||||||
|
let is_overlapping = cur.range.start < previous.range.end;
|
||||||
|
if is_overlapping {
|
||||||
|
quick_fixes.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let new_text =
|
||||||
|
deno_ast::apply_text_changes(text_info.text_str(), quick_fixes);
|
||||||
|
Some(new_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lint stdin and write result to stdout.
|
/// Lint stdin and write result to stdout.
|
||||||
|
@ -346,7 +474,7 @@ fn lint_file(
|
||||||
fn lint_stdin(
|
fn lint_stdin(
|
||||||
file_path: &Path,
|
file_path: &Path,
|
||||||
lint_rules: Vec<&'static dyn LintRule>,
|
lint_rules: Vec<&'static dyn LintRule>,
|
||||||
) -> Result<(Vec<LintDiagnostic>, ParsedSource), AnyError> {
|
) -> Result<(ParsedSource, Vec<LintDiagnostic>), AnyError> {
|
||||||
let mut source_code = String::new();
|
let mut source_code = String::new();
|
||||||
if stdin().read_to_string(&mut source_code).is_err() {
|
if stdin().read_to_string(&mut source_code).is_err() {
|
||||||
return Err(generic_error("Failed to read from stdin"));
|
return Err(generic_error("Failed to read from stdin"));
|
||||||
|
@ -354,24 +482,24 @@ fn lint_stdin(
|
||||||
|
|
||||||
let linter = create_linter(lint_rules);
|
let linter = create_linter(lint_rules);
|
||||||
|
|
||||||
let (source, file_diagnostics) = linter.lint_file(LintFileOptions {
|
linter
|
||||||
specifier: specifier_from_file_path(file_path)?,
|
.lint_file(LintFileOptions {
|
||||||
source_code: source_code.clone(),
|
specifier: specifier_from_file_path(file_path)?,
|
||||||
media_type: MediaType::TypeScript,
|
source_code: source_code.clone(),
|
||||||
})?;
|
media_type: MediaType::TypeScript,
|
||||||
|
})
|
||||||
Ok((file_diagnostics, source))
|
.map_err(AnyError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_lint_result(
|
fn handle_lint_result(
|
||||||
file_path: &str,
|
file_path: &str,
|
||||||
result: Result<(Vec<LintDiagnostic>, ParsedSource), AnyError>,
|
result: Result<(ParsedSource, Vec<LintDiagnostic>), AnyError>,
|
||||||
reporter_lock: Arc<Mutex<Box<dyn LintReporter + Send>>>,
|
reporter_lock: Arc<Mutex<Box<dyn LintReporter + Send>>>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut reporter = reporter_lock.lock();
|
let mut reporter = reporter_lock.lock();
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok((mut file_diagnostics, _source)) => {
|
Ok((_source, mut file_diagnostics)) => {
|
||||||
file_diagnostics.sort_by(|a, b| match a.specifier.cmp(&b.specifier) {
|
file_diagnostics.sort_by(|a, b| match a.specifier.cmp(&b.specifier) {
|
||||||
std::cmp::Ordering::Equal => a.range.start.cmp(&b.range.start),
|
std::cmp::Ordering::Equal => a.range.start.cmp(&b.range.start),
|
||||||
file_order => file_order,
|
file_order => file_order,
|
||||||
|
@ -493,17 +621,26 @@ struct LintError {
|
||||||
|
|
||||||
struct PrettyLintReporter {
|
struct PrettyLintReporter {
|
||||||
lint_count: u32,
|
lint_count: u32,
|
||||||
|
fixable_diagnostics: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrettyLintReporter {
|
impl PrettyLintReporter {
|
||||||
fn new() -> PrettyLintReporter {
|
fn new() -> PrettyLintReporter {
|
||||||
PrettyLintReporter { lint_count: 0 }
|
PrettyLintReporter {
|
||||||
|
lint_count: 0,
|
||||||
|
fixable_diagnostics: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LintReporter for PrettyLintReporter {
|
impl LintReporter for PrettyLintReporter {
|
||||||
fn visit_diagnostic(&mut self, d: LintOrCliDiagnostic) {
|
fn visit_diagnostic(&mut self, d: LintOrCliDiagnostic) {
|
||||||
self.lint_count += 1;
|
self.lint_count += 1;
|
||||||
|
if let LintOrCliDiagnostic::Lint(d) = d {
|
||||||
|
if !d.fixes.is_empty() {
|
||||||
|
self.fixable_diagnostics += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
eprintln!("{}", d.display());
|
eprintln!("{}", d.display());
|
||||||
}
|
}
|
||||||
|
@ -514,9 +651,17 @@ impl LintReporter for PrettyLintReporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close(&mut self, check_count: usize) {
|
fn close(&mut self, check_count: usize) {
|
||||||
|
let fixable_suffix = if self.fixable_diagnostics > 0 {
|
||||||
|
colors::gray(format!(" ({} fixable via --fix)", self.fixable_diagnostics))
|
||||||
|
.to_string()
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
};
|
||||||
match self.lint_count {
|
match self.lint_count {
|
||||||
1 => info!("Found 1 problem"),
|
1 => info!("Found 1 problem{}", fixable_suffix),
|
||||||
n if n > 1 => info!("Found {} problems", self.lint_count),
|
n if n > 1 => {
|
||||||
|
info!("Found {} problems{}", self.lint_count, fixable_suffix)
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10197,6 +10197,104 @@ console.log(snake_case);
|
||||||
client.shutdown();
|
client.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lsp_code_actions_lint_fixes() {
|
||||||
|
let context = TestContextBuilder::new().use_temp_cwd().build();
|
||||||
|
let mut client = context.new_lsp_command().build();
|
||||||
|
client.initialize_default();
|
||||||
|
let diagnostics = client.did_open(json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///a/file.ts",
|
||||||
|
"languageId": "typescript",
|
||||||
|
"version": 1,
|
||||||
|
"text": "window;",
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
let diagnostics = diagnostics.all();
|
||||||
|
let diagnostic = &diagnostics[0];
|
||||||
|
let res = client.write_request(
|
||||||
|
"textDocument/codeAction",
|
||||||
|
json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///a/file.ts"
|
||||||
|
},
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 0 },
|
||||||
|
"end": { "line": 0, "character": 6 }
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"diagnostics": [diagnostic],
|
||||||
|
"only": ["quickfix"]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
json!([{
|
||||||
|
"title": "Rename window to globalThis",
|
||||||
|
"kind": "quickfix",
|
||||||
|
"diagnostics": [diagnostic],
|
||||||
|
"edit": {
|
||||||
|
"changes": {
|
||||||
|
"file:///a/file.ts": [{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 0 },
|
||||||
|
"end": { "line": 0, "character": 6 }
|
||||||
|
},
|
||||||
|
"newText": "globalThis"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"title": "Disable no-window for this line",
|
||||||
|
"kind": "quickfix",
|
||||||
|
"diagnostics": [diagnostic],
|
||||||
|
"edit": {
|
||||||
|
"changes": {
|
||||||
|
"file:///a/file.ts": [{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 0 },
|
||||||
|
"end": { "line": 0, "character": 0 }
|
||||||
|
},
|
||||||
|
"newText": "// deno-lint-ignore no-window\n"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"title": "Disable no-window for the entire file",
|
||||||
|
"kind": "quickfix",
|
||||||
|
"diagnostics": [diagnostic],
|
||||||
|
"edit": {
|
||||||
|
"changes": {
|
||||||
|
"file:///a/file.ts": [{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 0 },
|
||||||
|
"end": { "line": 0, "character": 0 }
|
||||||
|
},
|
||||||
|
"newText": "// deno-lint-ignore-file no-window\n"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"title": "Ignore lint errors for the entire file",
|
||||||
|
"kind": "quickfix",
|
||||||
|
"diagnostics": [diagnostic],
|
||||||
|
"edit": {
|
||||||
|
"changes": {
|
||||||
|
"file:///a/file.ts": [{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 0 },
|
||||||
|
"end": { "line": 0, "character": 0 }
|
||||||
|
},
|
||||||
|
"newText": "// deno-lint-ignore-file\n"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
);
|
||||||
|
client.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lsp_lint_with_config() {
|
fn lsp_lint_with_config() {
|
||||||
let context = TestContextBuilder::new().use_temp_cwd().build();
|
let context = TestContextBuilder::new().use_temp_cwd().build();
|
||||||
|
|
17
tests/specs/lint/lint_fix/__test__.jsonc
Normal file
17
tests/specs/lint/lint_fix/__test__.jsonc
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"tempDir": true,
|
||||||
|
"steps": [{
|
||||||
|
"args": "lint --rules-tags=recommended,jsr",
|
||||||
|
"output": "lint.out",
|
||||||
|
"exitCode": 1
|
||||||
|
}, {
|
||||||
|
"args": "lint --fix --rules-tags=recommended,jsr",
|
||||||
|
"output": "lint_fixed.out"
|
||||||
|
}, {
|
||||||
|
"args": "lint --rules-tags=recommended,jsr",
|
||||||
|
"output": "lint_fixed.out"
|
||||||
|
}, {
|
||||||
|
"args": "run --allow-read --quiet http://localhost:4545/cat.ts a.ts",
|
||||||
|
"output": "a_fixed.out"
|
||||||
|
}]
|
||||||
|
}
|
4
tests/specs/lint/lint_fix/a.ts
Normal file
4
tests/specs/lint/lint_fix/a.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { Type } from "./test.ts";
|
||||||
|
export type MyType = Type;
|
||||||
|
console.log(window.value);
|
||||||
|
window.fetch;
|
4
tests/specs/lint/lint_fix/a_fixed.out
Normal file
4
tests/specs/lint/lint_fix/a_fixed.out
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import type { Type } from "./test.ts";
|
||||||
|
export type MyType = Type;
|
||||||
|
console.log(globalThis.value);
|
||||||
|
globalThis.fetch;
|
2
tests/specs/lint/lint_fix/lint.out
Normal file
2
tests/specs/lint/lint_fix/lint.out
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[WILDCARD]Found 4 problems (4 fixable via --fix)
|
||||||
|
Checked 1 file
|
1
tests/specs/lint/lint_fix/lint_fixed.out
Normal file
1
tests/specs/lint/lint_fix/lint_fixed.out
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Checked 1 file
|
12
tests/testdata/cat.ts
vendored
12
tests/testdata/cat.ts
vendored
|
@ -1,10 +1,4 @@
|
||||||
import { copy } from "../../tests/util/std/streams/copy.ts";
|
const filename = Deno.args[0];
|
||||||
async function main() {
|
using file = await Deno.open(filename);
|
||||||
for (let i = 1; i < Deno.args.length; i++) {
|
|
||||||
const filename = Deno.args[i];
|
|
||||||
const file = await Deno.open(filename);
|
|
||||||
await copy(file, Deno.stdout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
await file.readable.pipeTo(Deno.stdout.writable);
|
||||||
|
|
2
tests/testdata/lint/expected_from_stdin.out
vendored
2
tests/testdata/lint/expected_from_stdin.out
vendored
|
@ -5,7 +5,7 @@ error[no-explicit-any]: `any` type is not allowed
|
||||||
| ^^^
|
| ^^^
|
||||||
= hint: Use a specific type other than `any`
|
= hint: Use a specific type other than `any`
|
||||||
|
|
||||||
docs: https://lint.deno.land/#no-explicit-any
|
docs: https://lint.deno.land/rules/no-explicit-any
|
||||||
|
|
||||||
|
|
||||||
Found 1 problem
|
Found 1 problem
|
||||||
|
|
4
tests/testdata/lint/expected_quiet.out
vendored
4
tests/testdata/lint/expected_quiet.out
vendored
|
@ -5,7 +5,7 @@ error[ban-untagged-ignore]: Ignore directive requires lint rule name(s)
|
||||||
| ^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^
|
||||||
= hint: Add one or more lint rule names. E.g. // deno-lint-ignore adjacent-overload-signatures
|
= hint: Add one or more lint rule names. E.g. // deno-lint-ignore adjacent-overload-signatures
|
||||||
|
|
||||||
docs: https://lint.deno.land/#ban-untagged-ignore
|
docs: https://lint.deno.land/rules/ban-untagged-ignore
|
||||||
|
|
||||||
|
|
||||||
error[no-empty]: Empty block statement
|
error[no-empty]: Empty block statement
|
||||||
|
@ -15,6 +15,6 @@ error[no-empty]: Empty block statement
|
||||||
| ^^
|
| ^^
|
||||||
= hint: Add code or comment to the empty block
|
= hint: Add code or comment to the empty block
|
||||||
|
|
||||||
docs: https://lint.deno.land/#no-empty
|
docs: https://lint.deno.land/rules/no-empty
|
||||||
|
|
||||||
|
|
||||||
|
|
4
tests/testdata/lint/with_config.out
vendored
4
tests/testdata/lint/with_config.out
vendored
|
@ -5,7 +5,7 @@ error[ban-untagged-todo]: TODO should be tagged with (@username) or (#issue)
|
||||||
| ^^^^^^^^^^^^
|
| ^^^^^^^^^^^^
|
||||||
= hint: Add a user tag or issue reference to the TODO comment, e.g. TODO(@djones), TODO(djones), TODO(#123)
|
= hint: Add a user tag or issue reference to the TODO comment, e.g. TODO(@djones), TODO(djones), TODO(#123)
|
||||||
|
|
||||||
docs: https://lint.deno.land/#ban-untagged-todo
|
docs: https://lint.deno.land/rules/ban-untagged-todo
|
||||||
|
|
||||||
|
|
||||||
error[no-unused-vars]: `add` is never used
|
error[no-unused-vars]: `add` is never used
|
||||||
|
@ -15,7 +15,7 @@ error[no-unused-vars]: `add` is never used
|
||||||
| ^^^
|
| ^^^
|
||||||
= hint: If this is intentional, prefix it with an underscore like `_add`
|
= hint: If this is intentional, prefix it with an underscore like `_add`
|
||||||
|
|
||||||
docs: https://lint.deno.land/#no-unused-vars
|
docs: https://lint.deno.land/rules/no-unused-vars
|
||||||
|
|
||||||
|
|
||||||
Found 2 problems
|
Found 2 problems
|
||||||
|
|
|
@ -5,7 +5,7 @@ error[ban-untagged-todo]: TODO should be tagged with (@username) or (#issue)
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
= hint: Add a user tag or issue reference to the TODO comment, e.g. TODO(@djones), TODO(djones), TODO(#123)
|
= hint: Add a user tag or issue reference to the TODO comment, e.g. TODO(@djones), TODO(djones), TODO(#123)
|
||||||
|
|
||||||
docs: https://lint.deno.land/#ban-untagged-todo
|
docs: https://lint.deno.land/rules/ban-untagged-todo
|
||||||
|
|
||||||
|
|
||||||
error[no-unused-vars]: `subtract` is never used
|
error[no-unused-vars]: `subtract` is never used
|
||||||
|
@ -15,7 +15,7 @@ error[no-unused-vars]: `subtract` is never used
|
||||||
| ^^^^^^^^
|
| ^^^^^^^^
|
||||||
= hint: If this is intentional, prefix it with an underscore like `_subtract`
|
= hint: If this is intentional, prefix it with an underscore like `_subtract`
|
||||||
|
|
||||||
docs: https://lint.deno.land/#no-unused-vars
|
docs: https://lint.deno.land/rules/no-unused-vars
|
||||||
|
|
||||||
|
|
||||||
Found 2 problems
|
Found 2 problems
|
||||||
|
|
|
@ -5,7 +5,7 @@ error[ban-untagged-todo]: TODO should be tagged with (@username) or (#issue)
|
||||||
| ^^^^^^^^^^^^
|
| ^^^^^^^^^^^^
|
||||||
= hint: Add a user tag or issue reference to the TODO comment, e.g. TODO(@djones), TODO(djones), TODO(#123)
|
= hint: Add a user tag or issue reference to the TODO comment, e.g. TODO(@djones), TODO(djones), TODO(#123)
|
||||||
|
|
||||||
docs: https://lint.deno.land/#ban-untagged-todo
|
docs: https://lint.deno.land/rules/ban-untagged-todo
|
||||||
|
|
||||||
|
|
||||||
error[no-unused-vars]: `add` is never used
|
error[no-unused-vars]: `add` is never used
|
||||||
|
@ -15,7 +15,7 @@ error[no-unused-vars]: `add` is never used
|
||||||
| ^^^
|
| ^^^
|
||||||
= hint: If this is intentional, prefix it with an underscore like `_add`
|
= hint: If this is intentional, prefix it with an underscore like `_add`
|
||||||
|
|
||||||
docs: https://lint.deno.land/#no-unused-vars
|
docs: https://lint.deno.land/rules/no-unused-vars
|
||||||
|
|
||||||
|
|
||||||
Found 2 problems
|
Found 2 problems
|
||||||
|
|
Loading…
Reference in a new issue