@@ -35,6 +35,214 @@ describe('Validation Rules', () => {
3535 expect ( result . valid ) . toBe ( true ) ;
3636 } ) ;
3737
38+ it ( 'should detect object with toString arrow returning disallowed identifier' , async ( ) => {
39+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'constructor' ] } ) ;
40+ const validator = new JSAstValidator ( [ rule ] ) ;
41+
42+ const result = await validator . validate ( 'obj[{toString: () => "constructor"}]' , {
43+ rules : { 'disallowed-identifier' : true } ,
44+ } ) ;
45+ expect ( result . valid ) . toBe ( false ) ;
46+ expect ( result . issues [ 0 ] . code ) . toBe ( 'DISALLOWED_IDENTIFIER' ) ;
47+ expect ( result . issues [ 0 ] . data ?. [ 'identifier' ] ) . toBe ( 'constructor' ) ;
48+ } ) ;
49+
50+ it ( 'should detect object with toString method shorthand returning disallowed identifier' , async ( ) => {
51+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'constructor' ] } ) ;
52+ const validator = new JSAstValidator ( [ rule ] ) ;
53+
54+ const result = await validator . validate ( 'obj[{toString() { return "constructor" }}]' , {
55+ rules : { 'disallowed-identifier' : true } ,
56+ } ) ;
57+ expect ( result . valid ) . toBe ( false ) ;
58+ expect ( result . issues [ 0 ] . code ) . toBe ( 'DISALLOWED_IDENTIFIER' ) ;
59+ expect ( result . issues [ 0 ] . data ?. [ 'identifier' ] ) . toBe ( 'constructor' ) ;
60+ } ) ;
61+
62+ it ( 'should detect object with valueOf arrow returning disallowed identifier' , async ( ) => {
63+ const rule = new DisallowedIdentifierRule ( { disallowed : [ '__proto__' ] } ) ;
64+ const validator = new JSAstValidator ( [ rule ] ) ;
65+
66+ const result = await validator . validate ( 'obj[{valueOf: () => "__proto__"}]' , {
67+ rules : { 'disallowed-identifier' : true } ,
68+ } ) ;
69+ expect ( result . valid ) . toBe ( false ) ;
70+ expect ( result . issues [ 0 ] . code ) . toBe ( 'DISALLOWED_IDENTIFIER' ) ;
71+ expect ( result . issues [ 0 ] . data ?. [ 'identifier' ] ) . toBe ( '__proto__' ) ;
72+ } ) ;
73+
74+ it ( 'should detect object with toString function expression returning disallowed identifier' , async ( ) => {
75+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'prototype' ] } ) ;
76+ const validator = new JSAstValidator ( [ rule ] ) ;
77+
78+ const result = await validator . validate ( 'obj[{toString: function() { return "prototype" }}]' , {
79+ rules : { 'disallowed-identifier' : true } ,
80+ } ) ;
81+ expect ( result . valid ) . toBe ( false ) ;
82+ expect ( result . issues [ 0 ] . code ) . toBe ( 'DISALLOWED_IDENTIFIER' ) ;
83+ expect ( result . issues [ 0 ] . data ?. [ 'identifier' ] ) . toBe ( 'prototype' ) ;
84+ } ) ;
85+
86+ it ( 'should detect object inside array coercion' , async ( ) => {
87+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'constructor' ] } ) ;
88+ const validator = new JSAstValidator ( [ rule ] ) ;
89+
90+ const result = await validator . validate ( 'obj[[{toString: () => "constructor"}]]' , {
91+ rules : { 'disallowed-identifier' : true } ,
92+ } ) ;
93+ expect ( result . valid ) . toBe ( false ) ;
94+ expect ( result . issues [ 0 ] . code ) . toBe ( 'DISALLOWED_IDENTIFIER' ) ;
95+ expect ( result . issues [ 0 ] . data ?. [ 'identifier' ] ) . toBe ( 'constructor' ) ;
96+ } ) ;
97+
98+ it ( 'should not false positive on safe objects without toString/valueOf' , async ( ) => {
99+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'constructor' , '__proto__' ] } ) ;
100+ const validator = new JSAstValidator ( [ rule ] ) ;
101+
102+ const result = await validator . validate ( 'obj[{foo: "bar"}]' , {
103+ rules : { 'disallowed-identifier' : true } ,
104+ } ) ;
105+ expect ( result . valid ) . toBe ( true ) ;
106+ } ) ;
107+
108+ it ( 'should not false positive on toString returning non-disallowed string' , async ( ) => {
109+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'constructor' , '__proto__' ] } ) ;
110+ const validator = new JSAstValidator ( [ rule ] ) ;
111+
112+ const result = await validator . validate ( 'obj[{toString: () => "safe"}]' , {
113+ rules : { 'disallowed-identifier' : true } ,
114+ } ) ;
115+ expect ( result . valid ) . toBe ( true ) ;
116+ } ) ;
117+
118+ it ( 'should detect template literal key' , async ( ) => {
119+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'constructor' ] } ) ;
120+ const validator = new JSAstValidator ( [ rule ] ) ;
121+
122+ const result = await validator . validate ( 'obj[`constructor`]' , {
123+ rules : { 'disallowed-identifier' : true } ,
124+ } ) ;
125+ expect ( result . valid ) . toBe ( false ) ;
126+ expect ( result . issues [ 0 ] . code ) . toBe ( 'DISALLOWED_IDENTIFIER' ) ;
127+ expect ( result . issues [ 0 ] . data ?. [ 'identifier' ] ) . toBe ( 'constructor' ) ;
128+ } ) ;
129+
130+ it ( 'should detect conditional expression (consequent)' , async ( ) => {
131+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'constructor' ] } ) ;
132+ const validator = new JSAstValidator ( [ rule ] ) ;
133+
134+ const result = await validator . validate ( "obj[true ? 'constructor' : 'x']" , {
135+ rules : { 'disallowed-identifier' : true } ,
136+ } ) ;
137+ expect ( result . valid ) . toBe ( false ) ;
138+ expect ( result . issues [ 0 ] . code ) . toBe ( 'DISALLOWED_IDENTIFIER' ) ;
139+ expect ( result . issues [ 0 ] . data ?. [ 'identifier' ] ) . toBe ( 'constructor' ) ;
140+ } ) ;
141+
142+ it ( 'should detect conditional expression (alternate)' , async ( ) => {
143+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'constructor' ] } ) ;
144+ const validator = new JSAstValidator ( [ rule ] ) ;
145+
146+ const result = await validator . validate ( "obj[false ? 'x' : 'constructor']" , {
147+ rules : { 'disallowed-identifier' : true } ,
148+ } ) ;
149+ expect ( result . valid ) . toBe ( false ) ;
150+ expect ( result . issues [ 0 ] . code ) . toBe ( 'DISALLOWED_IDENTIFIER' ) ;
151+ expect ( result . issues [ 0 ] . data ?. [ 'identifier' ] ) . toBe ( 'constructor' ) ;
152+ } ) ;
153+
154+ it ( 'should detect sequence expression' , async ( ) => {
155+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'constructor' ] } ) ;
156+ const validator = new JSAstValidator ( [ rule ] ) ;
157+
158+ const result = await validator . validate ( "obj[(0, 'constructor')]" , {
159+ rules : { 'disallowed-identifier' : true } ,
160+ } ) ;
161+ expect ( result . valid ) . toBe ( false ) ;
162+ expect ( result . issues [ 0 ] . code ) . toBe ( 'DISALLOWED_IDENTIFIER' ) ;
163+ expect ( result . issues [ 0 ] . data ?. [ 'identifier' ] ) . toBe ( 'constructor' ) ;
164+ } ) ;
165+
166+ it ( 'should detect assignment expression as computed key' , async ( ) => {
167+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'constructor' ] } ) ;
168+ const validator = new JSAstValidator ( [ rule ] ) ;
169+
170+ const result = await validator . validate ( "let x; obj[x = 'constructor']" , {
171+ rules : { 'disallowed-identifier' : true } ,
172+ } ) ;
173+ expect ( result . valid ) . toBe ( false ) ;
174+ expect ( result . issues [ 0 ] . code ) . toBe ( 'DISALLOWED_IDENTIFIER' ) ;
175+ expect ( result . issues [ 0 ] . data ?. [ 'identifier' ] ) . toBe ( 'constructor' ) ;
176+ } ) ;
177+
178+ it ( 'should detect logical OR expression' , async ( ) => {
179+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'constructor' ] } ) ;
180+ const validator = new JSAstValidator ( [ rule ] ) ;
181+
182+ const result = await validator . validate ( "obj['' || 'constructor']" , {
183+ rules : { 'disallowed-identifier' : true } ,
184+ } ) ;
185+ expect ( result . valid ) . toBe ( false ) ;
186+ expect ( result . issues [ 0 ] . code ) . toBe ( 'DISALLOWED_IDENTIFIER' ) ;
187+ expect ( result . issues [ 0 ] . data ?. [ 'identifier' ] ) . toBe ( 'constructor' ) ;
188+ } ) ;
189+
190+ it ( 'should detect logical AND expression' , async ( ) => {
191+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'constructor' ] } ) ;
192+ const validator = new JSAstValidator ( [ rule ] ) ;
193+
194+ const result = await validator . validate ( "obj['constructor' && 'constructor']" , {
195+ rules : { 'disallowed-identifier' : true } ,
196+ } ) ;
197+ expect ( result . valid ) . toBe ( false ) ;
198+ expect ( result . issues [ 0 ] . code ) . toBe ( 'DISALLOWED_IDENTIFIER' ) ;
199+ expect ( result . issues [ 0 ] . data ?. [ 'identifier' ] ) . toBe ( 'constructor' ) ;
200+ } ) ;
201+
202+ it ( 'should detect nullish coalescing expression' , async ( ) => {
203+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'constructor' ] } ) ;
204+ const validator = new JSAstValidator ( [ rule ] ) ;
205+
206+ const result = await validator . validate ( "obj[null ?? 'constructor']" , {
207+ rules : { 'disallowed-identifier' : true } ,
208+ } ) ;
209+ expect ( result . valid ) . toBe ( false ) ;
210+ expect ( result . issues [ 0 ] . code ) . toBe ( 'DISALLOWED_IDENTIFIER' ) ;
211+ expect ( result . issues [ 0 ] . data ?. [ 'identifier' ] ) . toBe ( 'constructor' ) ;
212+ } ) ;
213+
214+ it ( 'should detect getter-based toString coercion' , async ( ) => {
215+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'constructor' ] } ) ;
216+ const validator = new JSAstValidator ( [ rule ] ) ;
217+
218+ const result = await validator . validate ( "obj[{get toString(){ return () => 'constructor' }}]" , {
219+ rules : { 'disallowed-identifier' : true } ,
220+ } ) ;
221+ expect ( result . valid ) . toBe ( false ) ;
222+ expect ( result . issues [ 0 ] . code ) . toBe ( 'DISALLOWED_IDENTIFIER' ) ;
223+ expect ( result . issues [ 0 ] . data ?. [ 'identifier' ] ) . toBe ( 'constructor' ) ;
224+ } ) ;
225+
226+ it ( 'should allow template literal with expressions (not statically resolvable)' , async ( ) => {
227+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'constructor' ] } ) ;
228+ const validator = new JSAstValidator ( [ rule ] ) ;
229+
230+ const result = await validator . validate ( "obj[`${'con'}structor`]" , {
231+ rules : { 'disallowed-identifier' : true } ,
232+ } ) ;
233+ expect ( result . valid ) . toBe ( true ) ;
234+ } ) ;
235+
236+ it ( 'should allow safe template literal' , async ( ) => {
237+ const rule = new DisallowedIdentifierRule ( { disallowed : [ 'constructor' ] } ) ;
238+ const validator = new JSAstValidator ( [ rule ] ) ;
239+
240+ const result = await validator . validate ( 'obj[`safe`]' , {
241+ rules : { 'disallowed-identifier' : true } ,
242+ } ) ;
243+ expect ( result . valid ) . toBe ( true ) ;
244+ } ) ;
245+
38246 it ( 'should use custom message template' , async ( ) => {
39247 const rule = new DisallowedIdentifierRule ( {
40248 disallowed : [ 'eval' ] ,
0 commit comments