@@ -14,6 +14,22 @@ const countChars = (str, ch) => {
14
14
return ( str . match ( new RegExp ( escapeStringRegexp ( ch ) , 'gu' ) ) || [ ] ) . length ;
15
15
} ;
16
16
17
+ const getRegexFromString = ( regexString ) => {
18
+ const match = regexString . match ( / ^ \/ ( .* ) \/ ( [ g i m y u s ] * ) $ / u) ;
19
+ let flags = 'u' ;
20
+ let regex = regexString ;
21
+ if ( match ) {
22
+ [ , regex , flags ] = match ;
23
+ if ( ! flags ) {
24
+ flags = 'u' ;
25
+ }
26
+ const uniqueFlags = [ ...new Set ( flags ) ] ;
27
+ flags = uniqueFlags . join ( '' ) ;
28
+ }
29
+
30
+ return new RegExp ( regex , flags ) ;
31
+ } ;
32
+
17
33
export default iterateJsdoc ( ( {
18
34
report,
19
35
utils,
@@ -67,8 +83,12 @@ export default iterateJsdoc(({
67
83
'padded-blocks' : 0 ,
68
84
} ;
69
85
70
- exampleCodeRegex = exampleCodeRegex && new RegExp ( exampleCodeRegex , 'u' ) ;
71
- rejectExampleCodeRegex = rejectExampleCodeRegex && new RegExp ( rejectExampleCodeRegex , 'u' ) ;
86
+ if ( exampleCodeRegex ) {
87
+ exampleCodeRegex = getRegexFromString ( exampleCodeRegex ) ;
88
+ }
89
+ if ( rejectExampleCodeRegex ) {
90
+ rejectExampleCodeRegex = getRegexFromString ( rejectExampleCodeRegex ) ;
91
+ }
72
92
73
93
utils . forEachPreferredTag ( 'example' , ( tag , targetTagName ) => {
74
94
// If a space is present, we should ignore it
@@ -90,40 +110,40 @@ export default iterateJsdoc(({
90
110
return ;
91
111
}
92
112
93
- let nonJSPrefacingLines = 0 ;
94
- let nonJSPrefacingCols = 0 ;
95
-
113
+ const sources = [ ] ;
96
114
if ( exampleCodeRegex ) {
97
- const idx = source . search ( exampleCodeRegex ) ;
115
+ let nonJSPrefacingCols = 0 ;
116
+ let nonJSPrefacingLines = 0 ;
98
117
99
- // Strip out anything preceding user regex match (can affect line numbering)
100
- const preMatch = source . slice ( 0 , idx ) ;
118
+ let startingIndex = 0 ;
119
+ let lastStringCount = 0 ;
101
120
102
- const preMatchLines = countChars ( preMatch , '\n' ) ;
121
+ let exampleCode ;
122
+ exampleCodeRegex . lastIndex = 0 ;
123
+ while ( ( exampleCode = exampleCodeRegex . exec ( source ) ) !== null ) {
124
+ const { index, 0 : n0 , 1 : n1 } = exampleCode ;
103
125
104
- nonJSPrefacingLines = preMatchLines ;
126
+ // Count anything preceding user regex match (can affect line numbering)
127
+ const preMatch = source . slice ( startingIndex , index ) ;
105
128
106
- const colDelta = preMatchLines ?
107
- preMatch . slice ( preMatch . lastIndexOf ( '\n' ) + 1 ) . length :
108
- preMatch . length ;
129
+ const preMatchLines = countChars ( preMatch , '\n' ) ;
109
130
110
- // Get rid of text preceding user regex match (even if it leaves valid JS, it
111
- // could cause us to count newlines twice)
112
- source = source . slice ( idx ) ;
131
+ const colDelta = preMatchLines ?
132
+ preMatch . slice ( preMatch . lastIndexOf ( '\n' ) + 1 ) . length :
133
+ preMatch . length ;
113
134
114
- source = source . replace ( exampleCodeRegex , ( n0 , n1 ) => {
115
135
let nonJSPreface ;
116
136
let nonJSPrefaceLineCount ;
117
137
if ( n1 ) {
118
- const index = n0 . indexOf ( n1 ) ;
119
- nonJSPreface = n0 . slice ( 0 , index ) ;
138
+ const idx = n0 . indexOf ( n1 ) ;
139
+ nonJSPreface = n0 . slice ( 0 , idx ) ;
120
140
nonJSPrefaceLineCount = countChars ( nonJSPreface , '\n' ) ;
121
141
} else {
122
142
nonJSPreface = '' ;
123
143
nonJSPrefaceLineCount = 0 ;
124
144
}
125
145
126
- nonJSPrefacingLines += nonJSPrefaceLineCount ;
146
+ nonJSPrefacingLines += lastStringCount + preMatchLines + nonJSPrefaceLineCount ;
127
147
128
148
// Ignore `preMatch` delta if newlines here
129
149
if ( nonJSPrefaceLineCount ) {
@@ -134,100 +154,125 @@ export default iterateJsdoc(({
134
154
nonJSPrefacingCols += colDelta + nonJSPreface . length ;
135
155
}
136
156
137
- return n1 || n0 ;
157
+ const string = n1 || n0 ;
158
+ sources . push ( {
159
+ nonJSPrefacingCols,
160
+ nonJSPrefacingLines,
161
+ string,
162
+ } ) ;
163
+ startingIndex = exampleCodeRegex . lastIndex ;
164
+ lastStringCount = countChars ( string , '\n' ) ;
165
+ if ( ! exampleCodeRegex . global ) {
166
+ break ;
167
+ }
168
+ }
169
+ } else {
170
+ sources . push ( {
171
+ nonJSPrefacingCols : 0 ,
172
+ nonJSPrefacingLines : 0 ,
173
+ string : source ,
138
174
} ) ;
139
175
}
140
176
141
- // Programmatic ESLint API: https://eslint.org/docs/developer-guide/nodejs-api
142
- const cli = new CLIEngine ( {
143
- allowInlineConfig,
144
- baseConfig,
145
- configFile,
146
- reportUnusedDisableDirectives,
147
- rulePaths,
148
- rules,
149
- useEslintrc : eslintrcForExamples ,
150
- } ) ;
151
-
152
- let messages ;
153
-
154
- if ( paddedIndent ) {
155
- source = source . replace ( new RegExp ( `(^|\n) {${ paddedIndent } }(?!$)` , 'gu' ) , '\n' ) ;
156
- }
157
-
158
- if ( filename ) {
159
- const config = cli . getConfigForFile ( filename ) ;
160
-
161
- // We need a new instance to ensure that the rules that may only
162
- // be available to `filename` (if it has its own `.eslintrc`),
163
- // will be defined.
164
- const cliFile = new CLIEngine ( {
177
+ // Todo: Make fixable
178
+ // Todo: Fix whitespace indent
179
+ const checkRules = function ( {
180
+ nonJSPrefacingCols,
181
+ nonJSPrefacingLines,
182
+ string,
183
+ } ) {
184
+ // Programmatic ESLint API: https://eslint.org/docs/developer-guide/nodejs-api
185
+ const cli = new CLIEngine ( {
165
186
allowInlineConfig,
166
- baseConfig : config ,
187
+ baseConfig,
167
188
configFile,
168
189
reportUnusedDisableDirectives,
169
190
rulePaths,
170
191
rules,
171
192
useEslintrc : eslintrcForExamples ,
172
193
} ) ;
173
194
174
- const linter = new Linter ( ) ;
195
+ let messages ;
175
196
176
- // Force external rules to become available on `cli`
177
- try {
178
- cliFile . executeOnText ( '' ) ;
179
- } catch ( error ) {
180
- // Ignore
197
+ let src = string ;
198
+ if ( paddedIndent ) {
199
+ src = src . replace ( new RegExp ( `(^|\n) {${ paddedIndent } }(?!$)` , 'gu' ) , '\n' ) ;
181
200
}
182
201
183
- const linterRules = [ ...cliFile . getRules ( ) . entries ( ) ] . reduce ( ( obj , [ key , val ] ) => {
184
- obj [ key ] = val ;
202
+ if ( filename ) {
203
+ const config = cli . getConfigForFile ( filename ) ;
204
+
205
+ // We need a new instance to ensure that the rules that may only
206
+ // be available to `filename` (if it has its own `.eslintrc`),
207
+ // will be defined.
208
+ const cliFile = new CLIEngine ( {
209
+ allowInlineConfig,
210
+ baseConfig : config ,
211
+ configFile,
212
+ reportUnusedDisableDirectives,
213
+ rulePaths,
214
+ rules,
215
+ useEslintrc : eslintrcForExamples ,
216
+ } ) ;
217
+
218
+ const linter = new Linter ( ) ;
219
+
220
+ // Force external rules to become available on `cli`
221
+ try {
222
+ cliFile . executeOnText ( '' ) ;
223
+ } catch ( error ) {
224
+ // Ignore
225
+ }
226
+
227
+ const linterRules = [ ...cliFile . getRules ( ) . entries ( ) ] . reduce ( ( obj , [ key , val ] ) => {
228
+ obj [ key ] = val ;
185
229
186
- return obj ;
187
- } , { } ) ;
230
+ return obj ;
231
+ } , { } ) ;
188
232
189
- linter . defineRules ( linterRules ) ;
233
+ linter . defineRules ( linterRules ) ;
234
+
235
+ if ( config . parser ) {
236
+ // eslint-disable-next-line global-require, import/no-dynamic-require
237
+ linter . defineParser ( config . parser , require ( config . parser ) ) ;
238
+ }
190
239
191
- if ( config . parser ) {
192
- // eslint-disable-next-line global-require, import/no-dynamic-require
193
- linter . defineParser ( config . parser , require ( config . parser ) ) ;
240
+ // Could also support `disableFixes` and `allowInlineConfig`
241
+ messages = linter . verify ( src , config , {
242
+ filename,
243
+ reportUnusedDisableDirectives,
244
+ } ) ;
245
+ } else {
246
+ ( { results : [ { messages} ] } =
247
+ cli . executeOnText ( src ) ) ;
194
248
}
195
249
196
- // Could also support `disableFixes` and `allowInlineConfig`
197
- messages = linter . verify ( source , config , {
198
- filename,
199
- reportUnusedDisableDirectives,
250
+ // NOTE: `tag.line` can be 0 if of form `/** @tag ... */`
251
+ const codeStartLine = tag . line + nonJSPrefacingLines ;
252
+ const codeStartCol = likelyNestedJSDocIndentSpace ;
253
+
254
+ messages . forEach ( ( { message, line, column, severity, ruleId} ) => {
255
+ const startLine = codeStartLine + line + zeroBasedLineIndexAdjust ;
256
+ const startCol = codeStartCol + (
257
+
258
+ // This might not work for line 0, but line 0 is unlikely for examples
259
+ line <= 1 ? nonJSPrefacingCols + firstLinePrefixLength : preTagSpaceLength
260
+ ) + column ;
261
+
262
+ report (
263
+ '@' + targetTagName + ' ' + ( severity === 2 ? 'error' : 'warning' ) +
264
+ ( ruleId ? ' (' + ruleId + ')' : '' ) + ': ' +
265
+ message ,
266
+ null ,
267
+ {
268
+ column : startCol ,
269
+ line : startLine ,
270
+ } ,
271
+ ) ;
200
272
} ) ;
201
- } else {
202
- ( { results : [ { messages} ] } =
203
- cli . executeOnText ( source ) ) ;
204
- }
273
+ } ;
205
274
206
- // Make fixable, fix whitespace indent, allow global regexes
207
- // NOTE: `tag.line` can be 0 if of form `/** @tag ... */`
208
- const codeStartLine = tag . line + nonJSPrefacingLines ;
209
- const codeStartCol = likelyNestedJSDocIndentSpace ;
210
-
211
- messages . forEach ( ( { message, line, column, severity, ruleId} ) => {
212
- const startLine = codeStartLine + line + zeroBasedLineIndexAdjust ;
213
- const startCol = codeStartCol + (
214
-
215
- // This might not work for line 0, but line 0 is unlikely for examples
216
- line <= 1 ? nonJSPrefacingCols + firstLinePrefixLength : preTagSpaceLength
217
- ) + column ;
218
-
219
- // Could perhaps make fixable
220
- report (
221
- '@' + targetTagName + ' ' + ( severity === 2 ? 'error' : 'warning' ) +
222
- ( ruleId ? ' (' + ruleId + ')' : '' ) + ': ' +
223
- message ,
224
- null ,
225
- {
226
- column : startCol ,
227
- line : startLine ,
228
- } ,
229
- ) ;
230
- } ) ;
275
+ sources . forEach ( checkRules ) ;
231
276
} ) ;
232
277
} , {
233
278
iterateAllJsdocs : true ,
0 commit comments