11import { Stats } from "fs" ;
22import { access , constants , lstat , readFile } from "fs/promises" ;
33import { minimatch } from "minimatch" ;
4+ import { createRequire } from "module" ;
45import { dirname , join , resolve , sep } from "path" ;
56import { sep as posixSep } from "path/posix" ;
7+ import vm from "vm" ;
68import { parse as yamlParse } from "yaml" ;
79import { z } from "zod" ;
810import { fromError } from "zod-validation-error" ;
911
1012export interface Suppression {
1113 tool : string ;
14+ // String of JavaScript CJS code, executed in a prepared context, that determines if a suppression should be included
15+ if ?: string ;
1216 // Output only exposes "paths". For input, if "path" is defined, it is inserted at the start of "paths".
1317 paths : string [ ] ;
1418 rules ?: string [ ] ;
@@ -20,6 +24,7 @@ const suppressionSchema = z.array(
2024 z
2125 . object ( {
2226 tool : z . string ( ) ,
27+ if : z . string ( ) . optional ( ) ,
2328 // For now, input allows "path" alongside "paths". Lather, may deprecate "path".
2429 path : z . string ( ) . optional ( ) ,
2530 paths : z . array ( z . string ( ) ) . optional ( ) ,
@@ -39,6 +44,7 @@ const suppressionSchema = z.array(
3944 }
4045 return {
4146 tool : s . tool ,
47+ if : s . if ,
4248 paths : paths ,
4349 rules : s . rules ,
4450 subRules : s [ "sub-rules" ] ,
@@ -70,7 +76,11 @@ const suppressionSchema = z.array(
7076 * );
7177 * ```
7278 */
73- export async function getSuppressions ( tool : string , path : string ) : Promise < Suppression [ ] > {
79+ export async function getSuppressions (
80+ tool : string ,
81+ path : string ,
82+ context : Record < string , any > = { } ,
83+ ) : Promise < Suppression [ ] > {
7484 path = resolve ( path ) ;
7585
7686 // If path doesn't exist, throw instead of returning "[]" to prevent confusion
@@ -86,6 +96,7 @@ export async function getSuppressions(tool: string, path: string): Promise<Suppr
8696 path ,
8797 suppressionsFile ,
8898 await readFile ( suppressionsFile , { encoding : "utf8" } ) ,
99+ context ,
89100 ) ,
90101 ) ;
91102 }
@@ -125,6 +136,7 @@ export function getSuppressionsFromYaml(
125136 path : string ,
126137 suppressionsFile : string ,
127138 suppressionsYaml : string ,
139+ context : Record < string , any > = { } ,
128140) : Suppression [ ] {
129141 path = resolve ( path ) ;
130142 suppressionsFile = resolve ( suppressionsFile ) ;
@@ -140,19 +152,28 @@ export function getSuppressionsFromYaml(
140152 throw fromError ( err ) ;
141153 }
142154
143- return suppressions
144- . filter ( ( s ) => s . tool === tool )
145- . filter ( ( s ) => {
146- // Minimatch only allows forward-slashes in patterns and input
147- const pathPosix : string = path . split ( sep ) . join ( posixSep ) ;
148-
149- return s . paths . some ( ( suppressionPath ) => {
150- const pattern : string = join ( dirname ( suppressionsFile ) , suppressionPath )
151- . split ( sep )
152- . join ( posixSep ) ;
153- return minimatch ( pathPosix , pattern ) ;
154- } ) ;
155- } ) ;
155+ // Make "require" available inside sandbox for CJS imports
156+ const sandbox = { ...context , require : createRequire ( import . meta. url ) } ;
157+
158+ return (
159+ suppressions
160+ // Tool name
161+ . filter ( ( s ) => s . tool === tool )
162+ // Path
163+ . filter ( ( s ) => {
164+ // Minimatch only allows forward-slashes in patterns and input
165+ const pathPosix : string = path . split ( sep ) . join ( posixSep ) ;
166+
167+ return s . paths . some ( ( suppressionPath ) => {
168+ const pattern : string = join ( dirname ( suppressionsFile ) , suppressionPath )
169+ . split ( sep )
170+ . join ( posixSep ) ;
171+ return minimatch ( pathPosix , pattern ) ;
172+ } ) ;
173+ } )
174+ // If
175+ . filter ( ( s ) => s . if === undefined || vm . runInNewContext ( s . if , sandbox ) )
176+ ) ;
156177}
157178
158179/**
0 commit comments