0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-04 09:57:11 -05:00

make js ranges utf16 and better error handling

This commit is contained in:
David Sherret 2025-02-04 15:59:20 -05:00
parent 24b597626b
commit 9d2ccda8c2
19 changed files with 226 additions and 14 deletions

View file

@ -197,7 +197,7 @@ impl CliLinter {
media_type,
&self.linter,
self.deno_lint_config.clone(),
source.text_info_lazy(),
&source,
&diagnostics,
&external_linter_container,
)?;
@ -215,7 +215,7 @@ impl CliLinter {
log::warn!(
concat!(
"Reached maximum number of fix iterations for '{}'. There's ",
"probably a bug in Deno. Please fix this file manually.",
"probably a bug in the lint rule. Please fix this file manually.",
),
specifier,
);
@ -243,25 +243,75 @@ fn apply_lint_fixes_and_relint(
media_type: MediaType,
linter: &DenoLintLinter,
config: DenoLintConfig,
text_info: &SourceTextInfo,
original_source: &ParsedSource,
diagnostics: &[LintDiagnostic],
external_linter_container: &ExternalLinterContainer,
) -> Result<Option<(ParsedSource, Vec<LintDiagnostic>)>, AnyError> {
let text_info = original_source.text_info_lazy();
let Some(new_text) = apply_lint_fixes(text_info, diagnostics) else {
return Ok(None);
};
let (source, diagnostics) = linter
.lint_file(LintFileOptions {
let lint_with_text = |new_text: String| {
let (source, diagnostics) = linter.lint_file(LintFileOptions {
specifier: specifier.clone(),
source_code: new_text,
media_type,
config,
config: config.clone(),
external_linter: external_linter_container.get_callback(),
})
.context(
"An applied lint fix caused a syntax error. Please report this bug.",
)?;
})?;
let mut new_diagnostics = source.diagnostics().clone();
new_diagnostics.retain(|d| !original_source.diagnostics().contains(d));
if let Some(diagnostic) = new_diagnostics.pop() {
return Err(AnyError::from(diagnostic));
}
Ok((source, diagnostics))
};
let (source, diagnostics) = match lint_with_text(new_text) {
Ok(result) => result,
Err(err) => {
let utf16_map = Utf16Map::new(text_info.text_str());
// figure out which diagnostic caused a syntax error
let mut diagnostics = diagnostics.to_vec();
while let Some(last_diagnostic) = diagnostics.pop() {
let Some(lint_fix) = last_diagnostic.details.fixes.first() else {
continue;
};
let success = match apply_lint_fixes(text_info, &diagnostics) {
Some(new_text) => lint_with_text(new_text).is_ok(),
None => true,
};
if success {
let mut changes_text = String::new();
for change in &lint_fix.changes {
let utf8_start =
(change.range.start - text_info.range().start) as u32;
let utf8_end = (change.range.end - text_info.range().start) as u32;
let utf16_start = utf16_map
.utf8_to_utf16_offset(utf8_start.into())
.unwrap_or(utf8_start.into());
let utf16_end = utf16_map
.utf8_to_utf16_offset(utf8_end.into())
.unwrap_or(utf8_end.into());
changes_text.push_str(&format!(
"Range: [{}, {}]\n",
u32::from(utf16_start),
u32::from(utf16_end)
));
changes_text.push_str(&format!("Text: {:?}\n\n", &change.new_text));
}
return Err(err).context(format!(
"The '{}' rule caused a syntax error applying '{}'.\n\n{}",
last_diagnostic.details.code, lint_fix.description, changes_text
));
}
}
return Err(err).context(
"A lint fix caused a syntax error. This is a bug in a lint rule.",
);
}
};
if let Some(err) = external_linter_container.take_error() {
return Err(err);

View file

@ -55,11 +55,19 @@ impl LintReporter for PrettyLintReporter {
fn visit_error(&mut self, file_path: &str, err: &AnyError) {
log::error!("Error linting: {file_path}");
let text =
if let Some(CoreError::Js(js_error)) = err.downcast_ref::<CoreError>() {
log::error!(" {}", format_js_error(js_error));
return;
format_js_error(js_error)
} else {
format!("{err:#}")
};
for line in text.split('\n') {
if line.is_empty() {
log::error!("");
} else {
log::error!(" {}", line);
}
}
log::error!(" {err}");
}
fn close(&mut self, check_count: usize) {

View file

@ -0,0 +1,6 @@
{
"tempDir": true,
"args": "lint --fix",
"output": "fix.out",
"exitCode": 1
}

View file

@ -0,0 +1,5 @@
{
"lint": {
"plugins": ["./plugin.ts"]
}
}

View file

@ -0,0 +1,11 @@
Error linting: [WILDLINE]main.ts
The 'test-plugin/my-rule' rule caused a syntax error applying 'Fix this test-plugin/my-rule problem'.
Range: [14, 18]
Text: "garbage test test"
: Expected a semicolon at file:///[WILDLINE]/main.ts:1:23
const value = garbage test test;
~~~~
Checked 2 files

View file

@ -0,0 +1,2 @@
const value = "𝄞";
console.log(value);

View file

@ -0,0 +1,20 @@
export default {
name: "test-plugin",
rules: {
"my-rule": {
create(context) {
return {
VariableDeclarator(node) {
context.report({
node: node.init,
message: 'should be equal to string "1"',
fix(fixer) {
return fixer.replaceText(node.init, "garbage test test");
},
});
},
};
},
},
},
};

View file

@ -0,0 +1,6 @@
{
"tempDir": true,
"args": "lint --fix",
"output": "fix.out",
"exitCode": 1
}

View file

@ -0,0 +1,5 @@
{
"lint": {
"plugins": ["./plugin.ts"]
}
}

View file

@ -0,0 +1,12 @@
Reached maximum number of fix iterations for 'file:///[WILDLINE]/main.ts'. There's probably a bug in the lint rule. Please fix this file manually.
error[test-plugin/my-rule]: should be equal to string "1"
--> [WILDLINE]main.ts:1:15
|
1 | const value = [WILDLINE];
| [WILDLINE]
docs: https://docs.deno.com/lint/rules/test-plugin/my-rule
Found 1 problem (1 fixable via --fix)
Checked 2 files

View file

@ -0,0 +1,2 @@
const value = "𝄞";
console.log(value);

View file

@ -0,0 +1,20 @@
export default {
name: "test-plugin",
rules: {
"my-rule": {
create(context) {
return {
VariableDeclarator(node) {
context.report({
node: node.init,
message: 'should be equal to string "1"',
fix(fixer) {
return fixer.replaceText(node.init, Date.now().toString());
},
});
},
};
},
},
},
};

View file

@ -0,0 +1,22 @@
{
"tests": {
"lint": {
"args": "lint",
"output": "lint.out",
"exitCode": 1
},
"fix": {
"tempDir": true,
"steps": [{
"args": "lint --fix",
"output": "fix.out"
}, {
"args": [
"eval",
"console.log(Deno.readTextFileSync('main.ts').trim())"
],
"output": "fixed.out"
}]
}
}
}

View file

@ -0,0 +1,5 @@
{
"lint": {
"plugins": ["./plugin.ts"]
}
}

View file

@ -0,0 +1 @@
Checked 2 files

View file

@ -0,0 +1,2 @@
const value = "1";
console.log(value);

View file

@ -0,0 +1,11 @@
error[test-plugin/my-rule]: should be equal to string "1"
--> [WILDLINE]main.ts:1:15
|
1 | const value = "𝄞";
| ^^^
docs: https://docs.deno.com/lint/rules/test-plugin/my-rule
Found 1 problem (1 fixable via --fix)
Checked 2 files

View file

@ -0,0 +1,2 @@
const value = "𝄞";
console.log(value);

View file

@ -0,0 +1,22 @@
export default {
name: "test-plugin",
rules: {
"my-rule": {
create(context) {
return {
VariableDeclarator(node) {
if (node.init.type !== "Literal" || node.init.value !== "1") {
context.report({
node: node.init,
message: 'should be equal to string "1"',
fix(fixer) {
return fixer.replaceText(node.init, '"1"');
},
});
}
},
};
},
},
},
};