Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/oxlint/src-js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Context } from './plugins/context.ts';
import type { CreateOnceRule, Plugin, Rule } from './plugins/load.ts';
import type { BeforeHook, Visitor, VisitorWithHooks } from './plugins/types.ts';

export type { Context, Diagnostic } from './plugins/context.ts';
export type { Context, Diagnostic, DiagnosticBase, DiagnosticWithLoc, DiagnosticWithNode } from './plugins/context.ts';
export type { Fix, Fixer, FixFn, Range } from './plugins/fix.ts';
export type { CreateOnceRule, CreateRule, Plugin, Rule } from './plugins/load.ts';
export type {
Expand Down
47 changes: 40 additions & 7 deletions apps/oxlint/src-js/plugins/context.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import { getFixes } from './fix.js';
import { SOURCE_CODE } from './source_code.js';
import { getIndexFromLoc, SOURCE_CODE } from './source_code.js';

import type { Fix, FixFn } from './fix.ts';
import type { SourceCode } from './source_code.ts';
import type { Node } from './types.ts';
import type { Location, Node } from './types.ts';

const { hasOwn } = Object;

// Diagnostic in form passed by user to `Context#report()`
export interface Diagnostic {
export type Diagnostic = DiagnosticWithNode | DiagnosticWithLoc;

export interface DiagnosticBase {
message: string;
node: Node;
fix?: FixFn;
}

export interface DiagnosticWithNode extends DiagnosticBase {
node: Node;
}

export interface DiagnosticWithLoc extends DiagnosticBase {
loc: Location;
}

// Diagnostic in form sent to Rust
interface DiagnosticReport {
message: string;
Expand Down Expand Up @@ -127,16 +138,38 @@ export class Context {
/**
* Report error.
* @param diagnostic - Diagnostic object
* @throws {TypeError} If `diagnostic` is invalid
*/
report(diagnostic: Diagnostic): void {
const internal = getInternal(this, 'report errors');

// TODO: Validate `diagnostic`
const { node } = diagnostic;
let start: number, end: number, loc: Location;
if (hasOwn(diagnostic, 'loc') && (loc = (diagnostic as DiagnosticWithLoc).loc) != null) {
// `loc`
if (typeof loc !== 'object') throw new TypeError('`loc` must be an object');
start = getIndexFromLoc(loc.start);
end = getIndexFromLoc(loc.end);
} else {
// `node`
const { node } = diagnostic as DiagnosticWithNode;
if (node == null) throw new TypeError('Either `node` or `loc` is required');
if (typeof node !== 'object') throw new TypeError('`node` must be an object');
({ start, end } = node);
// Do type validation checks here, to ensure no error in serialization / deserialization.
// Range validation happens on Rust side.
if (
typeof start !== 'number' || typeof end !== 'number' ||
start < 0 || end < 0 || (start | 0) !== start || (end | 0) !== end
) {
throw new TypeError('`node.start` and `node.end` must be non-negative integers');
}
}

diagnostics.push({
message: diagnostic.message,
start: node.start,
end: node.end,
start,
end,
ruleIndex: internal.ruleIndex,
fixes: getFixes(diagnostic, internal),
});
Expand Down
2 changes: 1 addition & 1 deletion apps/oxlint/src-js/plugins/source_code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ function getLocFromIndex(offset: number): LineColumn {
* @throws {TypeError|RangeError} If `loc` is not an object with a numeric `line` and `column`,
* or if the `line` is less than or equal to zero, or the line or column is out of the expected range.
*/
function getIndexFromLoc(loc: LineColumn): number {
export function getIndexFromLoc(loc: LineColumn): number {
if (loc !== null && typeof loc === 'object') {
const { line, column } = loc;
if (typeof line === 'number' && typeof column === 'number' && (line | 0) === line && (column | 0) === column) {
Expand Down
4 changes: 4 additions & 0 deletions apps/oxlint/test/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ describe('oxlint CLI', () => {
await testFixture('basic_custom_plugin_multiple_rules');
});

it('should support reporting diagnostic with `loc`', async () => {
await testFixture('diagnostic_loc');
});

it('should receive ESTree-compatible AST', async () => {
await testFixture('estree');
});
Expand Down
7 changes: 7 additions & 0 deletions apps/oxlint/test/fixtures/diagnostic_loc/.oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"jsPlugins": ["./plugin.ts"],
"categories": { "correctness": "off" },
"rules": {
"loc-plugin/no-bugger": "error"
}
}
3 changes: 3 additions & 0 deletions apps/oxlint/test/fixtures/diagnostic_loc/files/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
debugger;
debugger;
debugger;
43 changes: 43 additions & 0 deletions apps/oxlint/test/fixtures/diagnostic_loc/output.snap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Exit code
1

# stdout
```
x loc-plugin(no-bugger): Bugger!
,-[files/index.js:1:3]
1 | debugger;
: ^^^^^^
2 | debugger;
`----

x loc-plugin(no-bugger): Bugger debugger debug!
,-[files/index.js:1:3]
1 | ,-> debugger;
2 | | debugger;
3 | `-> debugger;
`----

x loc-plugin(no-bugger): Bugger!
,-[files/index.js:2:3]
1 | debugger;
2 | debugger;
: ^^^^^^
3 | debugger;
`----

x loc-plugin(no-bugger): Bugger!
,-[files/index.js:3:3]
2 | debugger;
3 | debugger;
: ^^^^^^
`----

Found 0 warnings and 4 errors.
Finished in Xms on 1 file using X threads.
```

# stderr
```
WARNING: JS plugins are experimental and not subject to semver.
Breaking changes are possible while JS plugins support is under development.
```
37 changes: 37 additions & 0 deletions apps/oxlint/test/fixtures/diagnostic_loc/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { Plugin } from '../../../dist/index.js';

const plugin: Plugin = {
meta: {
name: 'loc-plugin',
},
rules: {
'no-bugger': {
create(context) {
let debuggerCount = 0;
return {
Program(_node) {
context.report({
message: 'Bugger debugger debug!',
loc: {
start: { line: 1, column: 2 },
end: { line: 3, column: 5 },
},
});
},
DebuggerStatement(_node) {
debuggerCount++;
context.report({
message: 'Bugger!',
loc: {
start: { line: debuggerCount, column: 2 },
end: { line: debuggerCount, column: 8 },
},
});
},
};
},
},
},
};

export default plugin;
Loading