1- import { createRequire } from " node:module" ;
2- import { lint } from " ./bindings.js" ;
1+ import { createRequire } from ' node:module' ;
2+ import { lint } from ' ./bindings.js' ;
33import {
44 DATA_POINTER_POS_32 ,
55 SOURCE_LEN_OFFSET ,
6- } from "./generated/constants.cjs" ;
7- import { getErrorMessage } from "./utils.js" ;
8- import {
9- addVisitorToCompiled ,
10- compiledVisitor ,
11- finalizeCompiledVisitor ,
12- initCompiledVisitor ,
13- } from "./visitor.js" ;
6+ // TODO(camc314): we need to generate `.d.ts` file for this module.
7+ // @ts -expect-error
8+ } from './generated/constants.cjs' ;
9+ import { getErrorMessage } from './utils.js' ;
10+ import { addVisitorToCompiled , compiledVisitor , finalizeCompiledVisitor , initCompiledVisitor } from './visitor.js' ;
1411
1512// Import methods and objects from `oxc-parser`.
1613// Use `require` not `import` as `oxc-parser` uses `require` internally,
1714// and need to make sure get same instance of modules as it uses internally,
1815// otherwise `TOKEN` here won't be same `TOKEN` as used within `oxc-parser`.
1916const require = createRequire ( import . meta. url ) ;
20- const { TOKEN } = require ( " ../dist/parser/raw-transfer/lazy-common.cjs" ) ,
21- walkProgram = require ( " ../dist/parser/generated/lazy/walk.cjs" ) ;
17+ const { TOKEN } = require ( ' ../dist/parser/raw-transfer/lazy-common.cjs' ) ,
18+ walkProgram = require ( ' ../dist/parser/generated/lazy/walk.cjs' ) ;
2219
2320// --------------------
2421// Plugin loading
2522// --------------------
2623
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+
2756// Absolute paths of plugins which have been loaded
2857const registeredPluginPaths = new Set ( ) ;
2958
3059// Rule objects for loaded rules.
3160// Indexed by `ruleId`, passed to `lintFile`.
32- const registeredRules = [ ] ;
61+ const registeredRules : {
62+ rule : Rule ;
63+ context : Context ;
64+ } [ ] = [ ] ;
3365
3466/**
3567 * Load a plugin.
@@ -40,22 +72,22 @@ const registeredRules = [];
4072 * @param {string } path - Absolute path of plugin file
4173 * @returns {string } - JSON result
4274 */
43- async function loadPlugin ( path ) {
75+ async function loadPlugin ( path : string ) : Promise < string > {
4476 try {
4577 return await loadPluginImpl ( path ) ;
4678 } catch ( err ) {
4779 return JSON . stringify ( { Failure : getErrorMessage ( err ) } ) ;
4880 }
4981}
5082
51- async function loadPluginImpl ( path ) {
83+ async function loadPluginImpl ( path : string ) : Promise < string > {
5284 if ( registeredPluginPaths . has ( path ) ) {
5385 return JSON . stringify ( {
54- Failure : " This plugin has already been registered" ,
86+ Failure : ' This plugin has already been registered' ,
5587 } ) ;
5688 }
5789
58- const { default : plugin } = await import ( path ) ;
90+ const { default : plugin } = ( await import ( path ) ) as { default : Plugin } ;
5991
6092 registeredPluginPaths . add ( path ) ;
6193
@@ -75,7 +107,22 @@ async function loadPluginImpl(path) {
75107 return JSON . stringify ( { Success : { name : pluginName , offset, ruleNames } } ) ;
76108}
77109
78- 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 ;
79126
80127/**
81128 * Context class.
@@ -84,32 +131,27 @@ let setupContextForFile;
84131 */
85132class Context {
86133 // Full rule name, including plugin name e.g. `my-plugin/my-rule`.
87- id ;
134+ id : string ;
88135 // Index into `ruleIds` sent from Rust. Set before calling `rule`'s `create` method.
89- #ruleIndex;
136+ #ruleIndex: number ;
90137 // Absolute path of file being linted. Set before calling `rule`'s `create` method.
91- filename ;
138+ filename : string ;
92139 // Absolute path of file being linted. Set before calling `rule`'s `create` method.
93- physicalFilename ;
140+ physicalFilename : string ;
94141
95142 /**
96143 * @constructor
97- * @param { string } fullRuleName - Rule name, in form `<plugin>/<rule>`
144+ * @param fullRuleName - Rule name, in form `<plugin>/<rule>`
98145 */
99- constructor ( fullRuleName ) {
146+ constructor ( fullRuleName : string ) {
100147 this . id = fullRuleName ;
101148 }
102149
103150 /**
104151 * Report error.
105- * @param {Object } diagnostic - Diagnostic object
106- * @param {string } diagnostic.message - Error message
107- * @param {Object } diagnostic.loc - Node or loc object
108- * @param {number } diagnostic.loc.start - Start range of diagnostic
109- * @param {number } diagnostic.loc.end - End range of diagnostic
110- * @returns {undefined }
152+ * @param diagnostic - Diagnostic object
111153 */
112- report ( diagnostic ) {
154+ report ( diagnostic : Diagnostic ) : void {
113155 diagnostics . push ( {
114156 message : diagnostic . message ,
115157 loc : { start : diagnostic . node . start , end : diagnostic . node . end } ,
@@ -118,18 +160,6 @@ class Context {
118160 }
119161
120162 static {
121- /**
122- * Update a `Context` with file-specific data.
123- *
124- * We have to define this function within class body, as it's not possible to set private property
125- * `#ruleIndex` from outside the class.
126- * We don't use a normal class method, because we don't want to expose this to user.
127- *
128- * @param {Context } context - `Context` object
129- * @param {number } ruleIndex - Index of this rule within `ruleIds` passed from Rust
130- * @param {string } filePath - Absolute path of file being linted
131- * @returns {undefined }
132- */
133163 setupContextForFile = ( context , ruleIndex , filePath ) => {
134164 context . #ruleIndex = ruleIndex ;
135165 context . filename = filePath ;
@@ -141,48 +171,60 @@ class Context {
141171// --------------------
142172// Running rules
143173// --------------------
174+ interface BufferWithArrays extends Uint8Array {
175+ uint32 : Uint32Array ;
176+ float64 : Float64Array ;
177+ }
144178
145179// Buffers cache.
146180//
147181// All buffers sent from Rust are stored in this array, indexed by `bufferId` (also sent from Rust).
148182// Buffers are only added to this array, never removed, so no buffers will be garbage collected
149183// until the process exits.
150- const buffers = [ ] ;
184+ const buffers : ( BufferWithArrays | null ) [ ] = [ ] ;
151185
152186// Diagnostics array. Reused for every file.
153- const diagnostics = [ ] ;
187+ const diagnostics : DiagnosticReport [ ] = [ ] ;
154188
155189// Text decoder, for decoding source text from buffer
156- const textDecoder = new TextDecoder ( " utf-8" , { ignoreBOM : true } ) ;
190+ const textDecoder = new TextDecoder ( ' utf-8' , { ignoreBOM : true } ) ;
157191
158192// Run rules on a file.
159193//
160194// TODO(camc314): why do we have to destructure here?
161195// In `./bindings.d.ts`, it doesn't indicate that we have to
162196// (typed as `(filePath: string, bufferId: number, buffer: Uint8Array | undefined | null, ruleIds: number[])`).
163- function lintFile ( [ filePath , bufferId , buffer , ruleIds ] ) {
197+ function lintFile ( [ filePath , bufferId , buffer , ruleIds ] : [
198+ string ,
199+ number ,
200+ Uint8Array | undefined | null ,
201+ number [ ] ,
202+ ] ) {
164203 // If new buffer, add it to `buffers` array. Otherwise, get existing buffer from array.
165204 // Do this before checks below, to make sure buffer doesn't get garbage collected when not expected
166205 // if there's an error.
167206 // TODO: Is this enough to guarantee soundness?
168- if ( buffer !== null ) {
207+ let processedBuffer : BufferWithArrays | null ;
208+ if ( buffer !== null && buffer !== undefined ) {
169209 const { buffer : arrayBuffer , byteOffset } = buffer ;
170- buffer . uint32 = new Uint32Array ( arrayBuffer , byteOffset ) ;
171- 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 ) ;
172213
173214 while ( buffers . length <= bufferId ) {
174215 buffers . push ( null ) ;
175216 }
176- buffers [ bufferId ] = buffer ;
217+ buffers [ bufferId ] = bufferWithArrays ;
218+ processedBuffer = bufferWithArrays ;
177219 } else {
178- buffer = buffers [ bufferId ] ;
220+ processedBuffer = buffers [ bufferId ] ;
179221 }
180222
181- if ( typeof filePath !== " string" || filePath . length === 0 ) {
182- throw new Error ( " expected filePath to be a non-zero length string" ) ;
223+ if ( typeof filePath !== ' string' || filePath . length === 0 ) {
224+ throw new Error ( ' expected filePath to be a non-zero length string' ) ;
183225 }
184226 if ( ! Array . isArray ( ruleIds ) || ruleIds . length === 0 ) {
185- throw new Error ( " Expected `ruleIds` to be a non-zero len array" ) ;
227+ throw new Error ( ' Expected `ruleIds` to be a non-zero len array' ) ;
186228 }
187229
188230 // Get visitors for this file from all rules
@@ -200,15 +242,17 @@ function lintFile([filePath, bufferId, buffer, ruleIds]) {
200242 // Skip this if no visitors visit any nodes.
201243 // Some rules seen in the wild return an empty visitor object from `create` if some initial check fails
202244 // e.g. file extension is not one the rule acts on.
203- if ( needsVisit ) {
204- const { uint32 } = buffer ,
245+ if ( needsVisit && processedBuffer ) {
246+ const { uint32 } = processedBuffer ,
205247 programPos = uint32 [ DATA_POINTER_POS_32 ] ,
206248 sourceByteLen = uint32 [ ( programPos + SOURCE_LEN_OFFSET ) >> 2 ] ;
207249
208- const sourceText = textDecoder . decode ( buffer . subarray ( 0 , sourceByteLen ) ) ;
250+ const sourceText = textDecoder . decode (
251+ processedBuffer . subarray ( 0 , sourceByteLen ) ,
252+ ) ;
209253 const sourceIsAscii = sourceText . length === sourceByteLen ;
210254 const ast = {
211- buffer,
255+ buffer : processedBuffer ,
212256 sourceText,
213257 sourceByteLen,
214258 sourceIsAscii,
@@ -230,6 +274,7 @@ function lintFile([filePath, bufferId, buffer, ruleIds]) {
230274// --------------------
231275
232276// Call Rust, passing `loadPlugin` and `lintFile` as callbacks
277+ // TODO(camc314): why is there a `@ts-expect-error` here?
233278// @ts -expect-error
234279const success = await lint ( loadPlugin , lintFile ) ;
235280
0 commit comments