@@ -18,7 +18,7 @@ import {AxiosRequestConfig} from "axios";
1818import path from "node:path" ;
1919import { Argv } from "./argv.js" ;
2020
21- jsep . plugins . register ( jsepRegex ) ; // /pattern/flags literals
21+ jsep . plugins . register ( jsepRegex ) ; // /pattern/flags literals
2222jsep . addBinaryOp ( "=~" , 10 ) ; // regex match operator
2323jsep . addBinaryOp ( "!~" , 10 ) ; // regex non-match operator
2424
@@ -222,20 +222,13 @@ export class Utils {
222222 // Reconstruct the source string for an atomic (non-logical) jsep node.
223223 // Identifiers keep their name ($VAR), literals use their raw form so that
224224 // strings retain quotes and regexes retain slashes and flags.
225- private static _nodeToAtom ( node : jsep . Expression , envs : { [ key : string ] : string } ) : string {
225+ private static _nodeToAtom ( node : jsep . Expression ) : string {
226226 switch ( node . type ) {
227- case "Identifier" : {
228- const n = node as jsep . Identifier ;
229- return n . name ;
230- // return this.expandTextWith(n.name, {
231- // unescape: JSON.stringify("$"),
232- // variable: (name) => JSON.stringify(envs[name] ?? null).replaceAll("\\\\", "\\"),
233- // });
234- }
227+ case "Identifier" : return ( node as jsep . Identifier ) . name ;
235228 case "Literal" : return ( node as jsep . Literal ) . raw ;
236229 case "BinaryExpression" : {
237230 const n = node as jsep . BinaryExpression ;
238- return `${ Utils . _nodeToAtom ( n . left , envs ) } ${ n . operator } ${ Utils . _nodeToAtom ( n . right , envs ) } ` ;
231+ return `${ Utils . _nodeToAtom ( n . left ) } ${ n . operator } ${ Utils . _nodeToAtom ( n . right ) } ` ;
239232 }
240233 default : return "" ;
241234 }
@@ -317,7 +310,7 @@ export class Utils {
317310 const assertMsg = [
318311 "Error attempting to evaluate the following rules:" ,
319312 " rules:" ,
320- ` - if: '${ Utils . _nodeToAtom ( node , envs ) } '` ,
313+ ` - if: '${ Utils . _nodeToAtom ( node ) } '` ,
321314 "as" ,
322315 "```javascript" ,
323316 `${ evalStr } ` ,
@@ -328,10 +321,24 @@ export class Utils {
328321 return Boolean ( res ) ;
329322 }
330323 }
331- // assert (node.type === "Literal", `not a Literal: ${JSON.stringify(node)}`)
332- const res = Utils . _evaluateRuleIf ( Utils . _nodeToAtom ( node , envs ) , envs ) ;
333- // console.log(`${JSON.stringify(node)} -> ${res}`)
334- return res ;
324+ const atom = Utils . _nodeToAtom ( node ) ;
325+ let res ;
326+ try {
327+ ( globalThis as any ) . RE2JS = RE2JS ;
328+ res = ( 0 , eval ) ( atom ) ;
329+ delete ( globalThis as any ) . RE2JS ;
330+ } catch {
331+ assert ( false , [
332+ "Error attempting to evaluate the following rules:" ,
333+ " rules:" ,
334+ ` - if: '${ ruleIf } '` ,
335+ "as" ,
336+ "```javascript" ,
337+ `${ atom } ` ,
338+ "```" ,
339+ ] . join ( "\n" ) ) ;
340+ }
341+ return Boolean ( res ) ;
335342 } ;
336343
337344 let ast ;
@@ -352,113 +359,6 @@ export class Utils {
352359 return walk ( ast ! ) ;
353360 }
354361
355- static _evaluateRuleIf ( ruleIf : string | undefined , envs : { [ key : string ] : string } ) : boolean {
356- if ( ruleIf === undefined ) return true ;
357- assert ( ! / \$ \{ \w + \} / . test ( ruleIf ) , chalk `rules:rule if invalid expression syntax: {blueBright ${ ruleIf } }\nuse {green $VAR} not {red \${VAR\}} in rules:if` ) ;
358- let evalStr = ruleIf ;
359-
360- const flagsToBinary = ( flags : string ) : number => {
361- let binary = 0 ;
362- if ( flags . includes ( "i" ) ) {
363- binary |= RE2JS . CASE_INSENSITIVE ;
364- }
365- if ( flags . includes ( "s" ) ) {
366- binary |= RE2JS . DOTALL ;
367- }
368- if ( flags . includes ( "m" ) ) {
369- binary |= RE2JS . MULTILINE ;
370- }
371- return binary ;
372- } ;
373-
374- evalStr = this . expandTextWith ( evalStr , {
375- unescape : JSON . stringify ( "$" ) ,
376- variable : ( name ) => JSON . stringify ( envs [ name ] ?? null ) . replaceAll ( "\\\\" , "\\" ) ,
377- } ) ;
378- const expandedEvalStr = evalStr ;
379-
380- // Scenario when RHS is a <regex>
381- // https://regexr.com/85sjo
382- const pattern1 = / \s * (?< operator > (?: = ~ ) | (?: ! ~ ) ) \s * \/ (?< rhs > .* ?[ ^ \\ ] ) \/ (?< flags > [ i g m s u y ] * ) ( \s | $ | \) ) / g;
383- evalStr = evalStr . replaceAll ( pattern1 , ( _ , operator , rhs , flags , remainingTokens ) => {
384- let _operator ;
385- switch ( operator ) {
386- case "=~" :
387- _operator = "!=" ; // Matches are found (!= null)
388- break ;
389- case "!~" :
390- _operator = "==" ; // Matches are not found (== null)
391- break ;
392- default :
393- throw operator ;
394- }
395- const _rhs = JSON . stringify ( rhs ) ; // JSON.stringify for escaping `"`
396- const containsNonEscapedSlash = / (?< ! \\ ) \/ / . test ( _rhs ) ;
397- const assertMsg = [
398- "Error attempting to evaluate the following rules:" ,
399- " rules:" ,
400- ` - if: '${ expandedEvalStr } '` ,
401- "as rhs contains unescaped quote" ,
402- ] ;
403- assert ( ! containsNonEscapedSlash , assertMsg . join ( "\n" ) ) ;
404- const flagsBinary = flagsToBinary ( flags ) ;
405- return `.matchRE2JS(RE2JS.compile(${ _rhs } , ${ flagsBinary } )) ${ _operator } null${ remainingTokens } ` ;
406- } ) ;
407-
408- // Scenario when RHS is surrounded by single/double-quotes
409- // https://regexr.com/85t0g
410- const pattern2 = / \s * (?< operator > = ~ | ! ~ ) \s * ( [ " ' ] ) (?< rhs > (?: \\ .| [ ^ \\ ] ) * ?) \2/ g;
411- evalStr = evalStr . replaceAll ( pattern2 , ( _ , operator , __ , rhs ) => {
412- let _operator ;
413- switch ( operator ) {
414- case "=~" :
415- _operator = "!=" ; // Matches are found (!= null)
416- break ;
417- case "!~" :
418- _operator = "==" ; // Matches are not found (== null)
419- break ;
420- default :
421- throw operator ;
422- }
423-
424- const assertMsg = [
425- "RHS (${rhs}) must be a regex pattern. Do not rely on this behavior!" ,
426- "Refer to https://docs.gitlab.com/ee/ci/jobs/job_rules.html#unexpected-behavior-from-regular-expression-matching-with- for more info..." ,
427- ] ;
428- assert ( ( / \/ ( .* ) \/ ( \w * ) / . test ( rhs ) ) , assertMsg . join ( "\n" ) ) ;
429-
430- const regex = / \/ (?< pattern > .* ) \/ (?< flags > [ i g m s u y ] * ) / ;
431- const _rhs = rhs . replace ( regex , ( _ : string , pattern : string , flags : string ) => {
432- const flagsBinary = flagsToBinary ( flags ) ;
433- return `RE2JS.compile("${ pattern } ", ${ flagsBinary } )` ;
434- } ) ;
435- return `.matchRE2JS(${ _rhs } ) ${ _operator } null` ;
436- } ) ;
437-
438- evalStr = evalStr . replaceAll ( / n u l l .m a t c h R E 2 J S \( .+ ?\) \s * ! = \s * n u l l / g, "false" ) ;
439- evalStr = evalStr . replaceAll ( / n u l l .m a t c h R E 2 J S \( .+ ?\) \s * = = \s * n u l l / g, "true" ) ;
440-
441- evalStr = evalStr . trim ( ) ;
442-
443- let res ;
444- try {
445- ( globalThis as any ) . RE2JS = RE2JS ;
446- res = ( 0 , eval ) ( evalStr ) ; // indirect eval
447- delete ( globalThis as any ) . RE2JS ;
448- } catch {
449- const assertMsg = [
450- "Error attempting to evaluate the following rules:" ,
451- " rules:" ,
452- ` - if: '${ expandedEvalStr } '` ,
453- "as" ,
454- "```javascript" ,
455- `${ evalStr } ` ,
456- "```" ,
457- ] ;
458- assert ( false , assertMsg . join ( "\n" ) ) ;
459- }
460- return Boolean ( res ) ;
461- }
462362
463363 static evaluateRuleExist ( cwd : string , ruleExists : string [ ] | { paths : string [ ] } | undefined ) : boolean {
464364 if ( ruleExists === undefined ) return true ;
0 commit comments