@@ -25,29 +25,100 @@ function create(context: RuleContext): RuleListener {
2525 const expectCasing : CaseOption = context . options [ 0 ] ?? 'camelCase'
2626 const checker = getCasingChecker ( expectCasing )
2727 const allowArray : boolean = context . options [ 1 ] ?. allowArray
28+ const splitByDotsOption : boolean = context . options [ 1 ] ?. splitByDots
2829
2930 function reportUnknown ( reportNode : YAMLAST . YAMLNode ) {
3031 context . report ( {
3132 message : `Unexpected object key. Use ${ expectCasing } string key instead` ,
3233 loc : reportNode . loc
3334 } )
3435 }
35- function verifyKey (
36- key : string | number ,
37- reportNode : JSONAST . JSONNode | YAMLAST . YAMLNode
38- ) {
39- if ( typeof key === 'number' ) {
40- if ( ! allowArray ) {
36+ function verifyKeyForString (
37+ key : string ,
38+ reportNode :
39+ | JSONAST . JSONProperty [ 'key' ]
40+ | NonNullable < YAMLAST . YAMLPair [ 'key' ] >
41+ ) : void {
42+ for ( const target of splitByDotsOption && key . includes ( '.' )
43+ ? splitByDots ( key , reportNode )
44+ : [ { key, loc : reportNode . loc } ] ) {
45+ if ( ! checker ( target . key ) ) {
4146 context . report ( {
42- message : `Unexpected array element` ,
43- loc : reportNode . loc
47+ message : `"{{key}}" is not {{expectCasing}}` ,
48+ loc : target . loc ,
49+ data : {
50+ key : target . key ,
51+ expectCasing
52+ }
4453 } )
4554 }
46- } else {
47- if ( ! checker ( key ) ) {
48- context . report ( {
49- message : `"${ key } " is not ${ expectCasing } ` ,
50- loc : reportNode . loc
55+ }
56+ }
57+ function verifyKeyForNumber (
58+ key : number ,
59+ reportNode :
60+ | NonNullable < JSONAST . JSONArrayExpression [ 'elements' ] [ number ] >
61+ | NonNullable < YAMLAST . YAMLSequence [ 'entries' ] [ number ] >
62+ ) : void {
63+ if ( ! allowArray ) {
64+ context . report ( {
65+ message : `Unexpected array element` ,
66+ loc : reportNode . loc
67+ } )
68+ }
69+ }
70+
71+ function splitByDots (
72+ key : string ,
73+ reportNode :
74+ | JSONAST . JSONProperty [ 'key' ]
75+ | NonNullable < YAMLAST . YAMLPair [ 'key' ] >
76+ ) {
77+ const result : {
78+ key : string
79+ loc : JSONAST . SourceLocation
80+ } [ ] = [ ]
81+ let startIndex = 0
82+ let index
83+ while ( ( index = key . indexOf ( '.' , startIndex ) ) >= 0 ) {
84+ const getLoc = buildGetLocation ( startIndex , index )
85+ result . push ( {
86+ key : key . slice ( startIndex , index ) ,
87+ get loc ( ) {
88+ return getLoc ( )
89+ }
90+ } )
91+
92+ startIndex = index + 1
93+ }
94+
95+ const getLoc = buildGetLocation ( startIndex , key . length )
96+ result . push ( {
97+ key : key . slice ( startIndex , index ) ,
98+ get loc ( ) {
99+ return getLoc ( )
100+ }
101+ } )
102+
103+ return result
104+
105+ function buildGetLocation ( start : number , end : number ) {
106+ const offset =
107+ reportNode . type === 'JSONLiteral' ||
108+ ( reportNode . type === 'YAMLScalar' &&
109+ ( reportNode . style === 'double-quoted' ||
110+ reportNode . style === 'single-quoted' ) )
111+ ? reportNode . range [ 0 ] + 1
112+ : reportNode . range [ 0 ]
113+ let cachedLoc : JSONAST . SourceLocation | undefined
114+ return ( ) => {
115+ if ( cachedLoc ) {
116+ return cachedLoc
117+ }
118+ const sourceCode = context . getSourceCode ( )
119+ return ( cachedLoc = {
120+ start : sourceCode . getLocFromIndex ( offset + start ) ,
121+ end : sourceCode . getLocFromIndex ( offset + end )
51122 } )
52123 }
53124 }
@@ -81,7 +152,7 @@ function create(context: RuleContext): RuleListener {
81152 const key =
82153 node . key . type === 'JSONLiteral' ? `${ node . key . value } ` : node . key . name
83154
84- verifyKey ( key , node . key )
155+ verifyKeyForString ( key , node . key )
85156 } ,
86157 'JSONProperty:exit' ( ) {
87158 keyStack = keyStack . upper !
@@ -92,7 +163,7 @@ function create(context: RuleContext): RuleListener {
92163 }
93164 ) {
94165 const key = node . parent . elements . indexOf ( node )
95- verifyKey ( key , node )
166+ verifyKeyForNumber ( key , node )
96167 }
97168 }
98169 }
@@ -147,7 +218,7 @@ function create(context: RuleContext): RuleListener {
147218 } else if ( node . key . type === 'YAMLScalar' ) {
148219 const keyValue = node . key . value
149220 const key = typeof keyValue === 'string' ? keyValue : String ( keyValue )
150- verifyKey ( key , node . key )
221+ verifyKeyForString ( key , node . key )
151222 } else {
152223 reportUnknown ( node )
153224 }
@@ -164,7 +235,7 @@ function create(context: RuleContext): RuleListener {
164235 return
165236 }
166237 const key = node . parent . entries . indexOf ( node )
167- verifyKey ( key , node )
238+ verifyKeyForNumber ( key , node )
168239 }
169240 }
170241 }
@@ -232,6 +303,9 @@ export = createRule({
232303 properties : {
233304 allowArray : {
234305 type : 'boolean'
306+ } ,
307+ splitByDots : {
308+ type : 'boolean'
235309 }
236310 } ,
237311 additionalProperties : false
0 commit comments