11import { createRequire } from 'node:module' ;
22import { 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' ;
49import { getErrorMessage } from './utils.js' ;
510import { 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
2057const 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 */
77132class 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
148190const 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
226279const success = await lint ( loadPlugin , lintFile ) ;
227280
0 commit comments