Skip to content

Commit a9101da

Browse files
committed
refactor(oxlint2): add some basic types (#12529)
1 parent 5d46596 commit a9101da

File tree

5 files changed

+135
-76
lines changed

5 files changed

+135
-76
lines changed

napi/oxlint2/src-js/index.ts

Lines changed: 96 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { createRequire } from 'node:module';
22
import { lint } from './bindings.js';
3-
import { DATA_POINTER_POS_32, SOURCE_LEN_OFFSET } from './generated/constants.cjs';
3+
import {
4+
DATA_POINTER_POS_32,
5+
SOURCE_LEN_OFFSET,
6+
// TODO(camc314): we need to generate `.d.ts` file for this module.
7+
// @ts-expect-error
8+
} from './generated/constants.cjs';
49
import { getErrorMessage } from './utils.js';
510
import { addVisitorToCompiled, compiledVisitor, finalizeCompiledVisitor, initCompiledVisitor } from './visitor.js';
611

@@ -16,12 +21,47 @@ const { TOKEN } = require('../dist/parser/raw-transfer/lazy-common.cjs'),
1621
// Plugin loading
1722
// --------------------
1823

24+
interface Diagnostic {
25+
message: string;
26+
node: {
27+
start: number;
28+
end: number;
29+
[key: string]: unknown;
30+
};
31+
}
32+
33+
interface DiagnosticReport {
34+
message: string;
35+
loc: { start: number; end: number };
36+
ruleIndex: number;
37+
}
38+
39+
interface Visitor {
40+
[key: string]: (node: any) => void;
41+
}
42+
43+
interface Rule {
44+
create: (context: Context) => Visitor;
45+
}
46+
47+
interface Plugin {
48+
meta: {
49+
name: string;
50+
};
51+
rules: {
52+
[key: string]: Rule;
53+
};
54+
}
55+
1956
// Absolute paths of plugins which have been loaded
2057
const registeredPluginPaths = new Set();
2158

2259
// Rule objects for loaded rules.
2360
// Indexed by `ruleId`, passed to `lintFile`.
24-
const registeredRules = [];
61+
const registeredRules: {
62+
rule: Rule;
63+
context: Context;
64+
}[] = [];
2565

2666
/**
2767
* Load a plugin.
@@ -32,22 +72,22 @@ const registeredRules = [];
3272
* @param {string} path - Absolute path of plugin file
3373
* @returns {string} - JSON result
3474
*/
35-
async function loadPlugin(path) {
75+
async function loadPlugin(path: string): Promise<string> {
3676
try {
3777
return await loadPluginImpl(path);
3878
} catch (err) {
3979
return JSON.stringify({ Failure: getErrorMessage(err) });
4080
}
4181
}
4282

43-
async function loadPluginImpl(path) {
83+
async function loadPluginImpl(path: string): Promise<string> {
4484
if (registeredPluginPaths.has(path)) {
4585
return JSON.stringify({
4686
Failure: 'This plugin has already been registered',
4787
});
4888
}
4989

50-
const { default: plugin } = await import(path);
90+
const { default: plugin } = (await import(path)) as { default: Plugin };
5191

5292
registeredPluginPaths.add(path);
5393

@@ -67,7 +107,22 @@ async function loadPluginImpl(path) {
67107
return JSON.stringify({ Success: { name: pluginName, offset, ruleNames } });
68108
}
69109

70-
let setupContextForFile;
110+
/**
111+
* Update a `Context` with file-specific data.
112+
*
113+
* We have to define this function within class body, as it's not possible to set private property
114+
* `#ruleIndex` from outside the class.
115+
* We don't use a normal class method, because we don't want to expose this to user.
116+
*
117+
* @param context - `Context` object
118+
* @param ruleIndex - Index of this rule within `ruleIds` passed from Rust
119+
* @param filePath - Absolute path of file being linted
120+
*/
121+
let setupContextForFile: (
122+
context: Context,
123+
ruleIndex: number,
124+
filePath: string,
125+
) => void;
71126

72127
/**
73128
* Context class.
@@ -76,32 +131,27 @@ let setupContextForFile;
76131
*/
77132
class Context {
78133
// Full rule name, including plugin name e.g. `my-plugin/my-rule`.
79-
id;
134+
id: string;
80135
// Index into `ruleIds` sent from Rust. Set before calling `rule`'s `create` method.
81-
#ruleIndex;
136+
#ruleIndex: number;
82137
// Absolute path of file being linted. Set before calling `rule`'s `create` method.
83-
filename;
138+
filename: string;
84139
// Absolute path of file being linted. Set before calling `rule`'s `create` method.
85-
physicalFilename;
140+
physicalFilename: string;
86141

87142
/**
88143
* @constructor
89-
* @param {string} fullRuleName - Rule name, in form `<plugin>/<rule>`
144+
* @param fullRuleName - Rule name, in form `<plugin>/<rule>`
90145
*/
91-
constructor(fullRuleName) {
146+
constructor(fullRuleName: string) {
92147
this.id = fullRuleName;
93148
}
94149

95150
/**
96151
* Report error.
97-
* @param {Object} diagnostic - Diagnostic object
98-
* @param {string} diagnostic.message - Error message
99-
* @param {Object} diagnostic.loc - Node or loc object
100-
* @param {number} diagnostic.loc.start - Start range of diagnostic
101-
* @param {number} diagnostic.loc.end - End range of diagnostic
102-
* @returns {undefined}
152+
* @param diagnostic - Diagnostic object
103153
*/
104-
report(diagnostic) {
154+
report(diagnostic: Diagnostic): void {
105155
diagnostics.push({
106156
message: diagnostic.message,
107157
loc: { start: diagnostic.node.start, end: diagnostic.node.end },
@@ -110,18 +160,6 @@ class Context {
110160
}
111161

112162
static {
113-
/**
114-
* Update a `Context` with file-specific data.
115-
*
116-
* We have to define this function within class body, as it's not possible to set private property
117-
* `#ruleIndex` from outside the class.
118-
* We don't use a normal class method, because we don't want to expose this to user.
119-
*
120-
* @param {Context} context - `Context` object
121-
* @param {number} ruleIndex - Index of this rule within `ruleIds` passed from Rust
122-
* @param {string} filePath - Absolute path of file being linted
123-
* @returns {undefined}
124-
*/
125163
setupContextForFile = (context, ruleIndex, filePath) => {
126164
context.#ruleIndex = ruleIndex;
127165
context.filename = filePath;
@@ -133,16 +171,20 @@ class Context {
133171
// --------------------
134172
// Running rules
135173
// --------------------
174+
interface BufferWithArrays extends Uint8Array {
175+
uint32: Uint32Array;
176+
float64: Float64Array;
177+
}
136178

137179
// Buffers cache.
138180
//
139181
// All buffers sent from Rust are stored in this array, indexed by `bufferId` (also sent from Rust).
140182
// Buffers are only added to this array, never removed, so no buffers will be garbage collected
141183
// until the process exits.
142-
const buffers = [];
184+
const buffers: (BufferWithArrays | null)[] = [];
143185

144186
// Diagnostics array. Reused for every file.
145-
const diagnostics = [];
187+
const diagnostics: DiagnosticReport[] = [];
146188

147189
// Text decoder, for decoding source text from buffer
148190
const textDecoder = new TextDecoder('utf-8', { ignoreBOM: true });
@@ -152,22 +194,30 @@ const textDecoder = new TextDecoder('utf-8', { ignoreBOM: true });
152194
// TODO(camc314): why do we have to destructure here?
153195
// In `./bindings.d.ts`, it doesn't indicate that we have to
154196
// (typed as `(filePath: string, bufferId: number, buffer: Uint8Array | undefined | null, ruleIds: number[])`).
155-
function lintFile([filePath, bufferId, buffer, ruleIds]) {
197+
function lintFile([filePath, bufferId, buffer, ruleIds]: [
198+
string,
199+
number,
200+
Uint8Array | undefined | null,
201+
number[],
202+
]) {
156203
// If new buffer, add it to `buffers` array. Otherwise, get existing buffer from array.
157204
// Do this before checks below, to make sure buffer doesn't get garbage collected when not expected
158205
// if there's an error.
159206
// TODO: Is this enough to guarantee soundness?
160-
if (buffer !== null) {
207+
let processedBuffer: BufferWithArrays | null;
208+
if (buffer !== null && buffer !== undefined) {
161209
const { buffer: arrayBuffer, byteOffset } = buffer;
162-
buffer.uint32 = new Uint32Array(arrayBuffer, byteOffset);
163-
buffer.float64 = new Float64Array(arrayBuffer, byteOffset);
210+
const bufferWithArrays = buffer as BufferWithArrays;
211+
bufferWithArrays.uint32 = new Uint32Array(arrayBuffer, byteOffset);
212+
bufferWithArrays.float64 = new Float64Array(arrayBuffer, byteOffset);
164213

165214
while (buffers.length <= bufferId) {
166215
buffers.push(null);
167216
}
168-
buffers[bufferId] = buffer;
217+
buffers[bufferId] = bufferWithArrays;
218+
processedBuffer = bufferWithArrays;
169219
} else {
170-
buffer = buffers[bufferId];
220+
processedBuffer = buffers[bufferId];
171221
}
172222

173223
if (typeof filePath !== 'string' || filePath.length === 0) {
@@ -192,15 +242,17 @@ function lintFile([filePath, bufferId, buffer, ruleIds]) {
192242
// Skip this if no visitors visit any nodes.
193243
// Some rules seen in the wild return an empty visitor object from `create` if some initial check fails
194244
// e.g. file extension is not one the rule acts on.
195-
if (needsVisit) {
196-
const { uint32 } = buffer,
245+
if (needsVisit && processedBuffer) {
246+
const { uint32 } = processedBuffer,
197247
programPos = uint32[DATA_POINTER_POS_32],
198248
sourceByteLen = uint32[(programPos + SOURCE_LEN_OFFSET) >> 2];
199249

200-
const sourceText = textDecoder.decode(buffer.subarray(0, sourceByteLen));
250+
const sourceText = textDecoder.decode(
251+
processedBuffer.subarray(0, sourceByteLen),
252+
);
201253
const sourceIsAscii = sourceText.length === sourceByteLen;
202254
const ast = {
203-
buffer,
255+
buffer: processedBuffer,
204256
sourceText,
205257
sourceByteLen,
206258
sourceIsAscii,
@@ -222,6 +274,7 @@ function lintFile([filePath, bufferId, buffer, ruleIds]) {
222274
// --------------------
223275

224276
// Call Rust, passing `loadPlugin` and `lintFile` as callbacks
277+
// TODO(camc314): why is there a `@ts-expect-error` here?
225278
// @ts-expect-error
226279
const success = await lint(loadPlugin, lintFile);
227280

napi/oxlint2/src-js/utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
* * `err` is an object with a getter for `message` property which throws.
1010
* * `err` has a getter for `message` property which returns a different value each time it's accessed.
1111
*
12-
* @param {*} err - Error
13-
* @returns {string} - Error message
12+
* @param err - Error
13+
* @returns Error message
1414
*/
15-
export function getErrorMessage(err) {
15+
export function getErrorMessage(err: unknown): string {
1616
try {
17-
const { message } = err;
17+
const { message } = err as undefined | { message: string };
1818
if (typeof message === 'string' && message !== '') return message;
1919
} catch {}
2020

0 commit comments

Comments
 (0)