@@ -25,29 +25,100 @@ function create(context: RuleContext): RuleListener {
25
25
const expectCasing : CaseOption = context . options [ 0 ] ?? 'camelCase'
26
26
const checker = getCasingChecker ( expectCasing )
27
27
const allowArray : boolean = context . options [ 1 ] ?. allowArray
28
+ const splitByDotsOption : boolean = context . options [ 1 ] ?. splitByDots
28
29
29
30
function reportUnknown ( reportNode : YAMLAST . YAMLNode ) {
30
31
context . report ( {
31
32
message : `Unexpected object key. Use ${ expectCasing } string key instead` ,
32
33
loc : reportNode . loc
33
34
} )
34
35
}
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 ) ) {
41
46
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
+ }
44
53
} )
45
54
}
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 )
51
122
} )
52
123
}
53
124
}
@@ -81,7 +152,7 @@ function create(context: RuleContext): RuleListener {
81
152
const key =
82
153
node . key . type === 'JSONLiteral' ? `${ node . key . value } ` : node . key . name
83
154
84
- verifyKey ( key , node . key )
155
+ verifyKeyForString ( key , node . key )
85
156
} ,
86
157
'JSONProperty:exit' ( ) {
87
158
keyStack = keyStack . upper !
@@ -92,7 +163,7 @@ function create(context: RuleContext): RuleListener {
92
163
}
93
164
) {
94
165
const key = node . parent . elements . indexOf ( node )
95
- verifyKey ( key , node )
166
+ verifyKeyForNumber ( key , node )
96
167
}
97
168
}
98
169
}
@@ -147,7 +218,7 @@ function create(context: RuleContext): RuleListener {
147
218
} else if ( node . key . type === 'YAMLScalar' ) {
148
219
const keyValue = node . key . value
149
220
const key = typeof keyValue === 'string' ? keyValue : String ( keyValue )
150
- verifyKey ( key , node . key )
221
+ verifyKeyForString ( key , node . key )
151
222
} else {
152
223
reportUnknown ( node )
153
224
}
@@ -164,7 +235,7 @@ function create(context: RuleContext): RuleListener {
164
235
return
165
236
}
166
237
const key = node . parent . entries . indexOf ( node )
167
- verifyKey ( key , node )
238
+ verifyKeyForNumber ( key , node )
168
239
}
169
240
}
170
241
}
@@ -232,6 +303,9 @@ export = createRule({
232
303
properties : {
233
304
allowArray : {
234
305
type : 'boolean'
306
+ } ,
307
+ splitByDots : {
308
+ type : 'boolean'
235
309
}
236
310
} ,
237
311
additionalProperties : false
0 commit comments