Skip to content

Commit 8c8c8bc

Browse files
committed
refactor(napi/oxlint): diagnostics communicate which rule via rule index, not rule ID (#12482)
Related to #12437. Diagnostics sent from JS to Rust specify which rule the diagnostic relates to via an index into `external_rules`, instead of the `ExternalRuleId`. This allows removing the loop over `external_rules` on Rust side to find the relevant rule.
1 parent 69f8b63 commit 8c8c8bc

File tree

5 files changed

+54
-46
lines changed

5 files changed

+54
-46
lines changed

crates/oxc_linter/src/config/config_store.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,10 @@ impl ConfigStore {
286286
}
287287

288288
#[cfg_attr(not(all(feature = "oxlint2", not(feature = "disable_oxlint2"))), expect(dead_code))]
289-
pub(crate) fn resolve_plugin_rule_names(&self, external_rule_id: u32) -> Option<(&str, &str)> {
289+
pub(crate) fn resolve_plugin_rule_names(
290+
&self,
291+
external_rule_id: ExternalRuleId,
292+
) -> (/* plugin name */ &str, /* rule name */ &str) {
290293
self.external_plugin_store.resolve_plugin_rule_names(external_rule_id)
291294
}
292295
}

crates/oxc_linter/src/external_linter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub enum PluginLoadResult {
4242
#[derive(Clone, Debug, Deserialize, Serialize)]
4343
#[serde(rename_all = "camelCase")]
4444
pub struct LintResult {
45-
pub external_rule_id: u32,
45+
pub rule_index: u32,
4646
pub message: String,
4747
pub loc: Loc,
4848
}

crates/oxc_linter/src/external_plugin_store.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,13 @@ impl ExternalPluginStore {
7979
})
8080
}
8181

82-
pub fn resolve_plugin_rule_names(&self, external_rule_id: u32) -> Option<(&str, &str)> {
83-
let external_rule = self.rules.get(ExternalRuleId::from_raw(external_rule_id))?;
82+
pub fn resolve_plugin_rule_names(
83+
&self,
84+
external_rule_id: ExternalRuleId,
85+
) -> (/* plugin name */ &str, /* rule name */ &str) {
86+
let external_rule = &self.rules[external_rule_id];
8487
let plugin = &self.plugins[external_rule.plugin_id];
85-
86-
Some((&plugin.name, &external_rule.name))
88+
(&plugin.name, &external_rule.name)
8789
}
8890
}
8991

crates/oxc_linter/src/lib.rs

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -266,33 +266,18 @@ impl Linter {
266266
match result {
267267
Ok(diagnostics) => {
268268
for diagnostic in diagnostics {
269-
match self.config.resolve_plugin_rule_names(diagnostic.external_rule_id) {
270-
Some((plugin_name, rule_name)) => {
271-
ctx_host.push_diagnostic(Message::new(
272-
// TODO: `error` isn't right, we need to get the severity from `external_rules`
273-
OxcDiagnostic::error(diagnostic.message)
274-
.with_label(Span::new(diagnostic.loc.start, diagnostic.loc.end))
275-
.with_error_code(plugin_name.to_string(), rule_name.to_string())
276-
.with_severity(
277-
(*external_rules
278-
.iter()
279-
.find(|(rule_id, _)| {
280-
rule_id.raw() == diagnostic.external_rule_id
281-
})
282-
.map(|(_, severity)| severity)
283-
.expect(
284-
"external rule must exist when resolving severity",
285-
))
286-
.into(),
287-
),
288-
PossibleFixes::None,
289-
));
290-
}
291-
None => {
292-
// TODO: report diagnostic, this should be unreachable
293-
debug_assert!(false);
294-
}
295-
}
269+
let (external_rule_id, severity) =
270+
external_rules[diagnostic.rule_index as usize];
271+
let (plugin_name, rule_name) =
272+
self.config.resolve_plugin_rule_names(external_rule_id);
273+
274+
ctx_host.push_diagnostic(Message::new(
275+
OxcDiagnostic::error(diagnostic.message)
276+
.with_label(Span::new(diagnostic.loc.start, diagnostic.loc.end))
277+
.with_error_code(plugin_name.to_string(), rule_name.to_string())
278+
.with_severity(severity.into()),
279+
PossibleFixes::None,
280+
));
296281
}
297282
}
298283
Err(_err) => {

napi/oxlint2/src-js/index.js

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,39 +51,37 @@ async function loadPluginImpl(path) {
5151

5252
// TODO: Use a validation library to assert the shape of the plugin, and of rules
5353
const pluginName = plugin.meta.name;
54-
let ruleId = registeredRules.length;
54+
const offset = registeredRules.length;
5555
const ruleNames = [];
56-
const ret = { name: pluginName, offset: ruleId, ruleNames };
5756

5857
for (const [ruleName, rule] of Object.entries(plugin.rules)) {
5958
ruleNames.push(ruleName);
60-
registeredRules.push({ rule, context: new Context(ruleId, `${pluginName}/${ruleName}`) });
61-
ruleId++;
59+
registeredRules.push({ rule, context: new Context(`${pluginName}/${ruleName}`) });
6260
}
6361

64-
return JSON.stringify({ Success: ret });
62+
return JSON.stringify({ Success: { name: pluginName, offset, ruleNames } });
6563
}
6664

65+
let setupContextForFile;
66+
6767
/**
6868
* Context class.
6969
*
7070
* Each rule has its own `Context` object. It is passed to that rule's `create` function.
7171
*/
7272
class Context {
73-
// Rule ID. Index into `registeredRules` array.
74-
#ruleId;
7573
// Full rule name, including plugin name e.g. `my-plugin/my-rule`.
7674
id;
75+
// Index into `ruleIds` sent from Rust. Set before calling `rule`'s `create` method.
76+
#ruleIndex;
7777
// Absolute path of file being linted. Set before calling `rule`'s `create` method.
7878
physicalFilename;
7979

8080
/**
8181
* @constructor
82-
* @param {number} ruleId - Rule ID
8382
* @param {string} fullRuleName - Rule name, in form `<plugin>/<rule>`
8483
*/
85-
constructor(ruleId, fullRuleName) {
86-
this.#ruleId = ruleId;
84+
constructor(fullRuleName) {
8785
this.id = fullRuleName;
8886
}
8987

@@ -100,9 +98,28 @@ class Context {
10098
diagnostics.push({
10199
message: diagnostic.message,
102100
loc: { start: diagnostic.node.start, end: diagnostic.node.end },
103-
externalRuleId: this.#ruleId,
101+
ruleIndex: this.#ruleIndex,
104102
});
105103
}
104+
105+
static {
106+
/**
107+
* Update a `Context` with file-specific data.
108+
*
109+
* We have to define this function within class body, as it's not possible to set private property
110+
* `#ruleIndex` from outside the class.
111+
* We don't use a normal class method, because we don't want to expose this to user.
112+
*
113+
* @param {Context} context - `Context` object
114+
* @param {number} ruleIndex - Index of this rule within `ruleIds` passed from Rust
115+
* @param {string} filePath - Absolute path of file being linted
116+
* @returns {undefined}
117+
*/
118+
setupContextForFile = (context, ruleIndex, filePath) => {
119+
context.#ruleIndex = ruleIndex;
120+
context.physicalFilename = filePath;
121+
};
122+
}
106123
}
107124

108125
// --------------------
@@ -154,9 +171,10 @@ function lintFile([filePath, bufferId, buffer, ruleIds]) {
154171

155172
// Get visitors for this file from all rules
156173
initCompiledVisitor();
157-
for (const ruleId of ruleIds) {
174+
for (let i = 0; i < ruleIds.length; i++) {
175+
const ruleId = ruleIds[i];
158176
const { rule: { create }, context } = registeredRules[ruleId];
159-
context.physicalFilename = filePath;
177+
setupContextForFile(context, i, filePath);
160178
const visitor = create(context);
161179
addVisitorToCompiled(visitor);
162180
}

0 commit comments

Comments
 (0)