Total Complexity | 54 |
Complexity/F | 3.38 |
Lines of Code | 284 |
Function Count | 16 |
Duplicated Lines | 0 |
Ratio | 0 % |
Changes | 3 | ||
Bugs | 0 | Features | 0 |
Complex classes like src/helpers.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | /** findMdpInsert and findCode functions use a similar layout to return the location and contents |
||
18 | export function findCode (txt, start) { |
||
19 | /** |
||
20 | * finds the next code in the string provided starting at position start |
||
21 | * returns an object containing start, length, internalStart, internalLength |
||
22 | * |
||
23 | * there are three types of code insertion - code span (inline), fenced code and indented code |
||
24 | * |
||
25 | * eg. span (1 or more backticks): |
||
26 | * some text ``echo myfile.txt`` more text |
||
27 | * |
||
28 | * eg. indented (4 or more indent spaces): |
||
29 | * some text |
||
30 | * function test() { |
||
31 | * console.log('test') |
||
32 | * } |
||
33 | * more text |
||
34 | * |
||
35 | * eg. fenced (3 or more backticks on a row on their own) |
||
36 | * some text |
||
37 | * ``` js |
||
38 | * function test() { |
||
39 | * console.log('test') |
||
40 | * } |
||
41 | * ``` |
||
42 | * more text |
||
43 | * |
||
44 | **/ |
||
45 | let x = _findFencedCode(txt, start) |
||
46 | let y = _findIndentedCode(txt, start) |
||
47 | let z = _findCodeSpan(txt, start) |
||
48 | |||
49 | return earlierOf(x, earlierOf(y, z)) |
||
50 | } |
||
51 | |||
52 | export function findMdpCode (txt, start) { |
||
53 | let posn = start |
||
54 | let x |
||
55 | while (true) { |
||
56 | x = findCode(txt, posn) |
||
57 | if (x.start === -1 || x.commandString.indexOf('mdpInsert ') !== -1) { |
||
58 | return x |
||
59 | } else { |
||
60 | posn = x.start + x.length |
||
61 | } |
||
62 | } |
||
1 ignored issue
–
show
|
|||
63 | } |
||
64 | |||
65 | export function findMdpInsert (txt, start) { |
||
66 | let s = _findMdpStartUnfenced(txt, start) |
||
67 | if (s.start === -1) { return s } |
||
68 | let s1 = JSON.parse(JSON.stringify(s)) // create copy |
||
69 | let depth = 1 |
||
70 | let e |
||
71 | let posn = s1.internalStart |
||
72 | while (depth !== 0) { |
||
73 | e = _findMdpEndUnfenced(txt, s, posn) |
||
74 | if (e.start === -1) { |
||
75 | // we have not found any more ends so we need to return a fail |
||
76 | return e |
||
77 | } |
||
78 | s1 = _findMdpStartUnfenced(txt, posn) |
||
79 | if (s1.start !== -1) { |
||
80 | // we have found another start pattern |
||
81 | if (s1.start < (e.internalStart + e.internalLength)) { |
||
82 | depth++ |
||
83 | posn = s1.internalStart |
||
84 | } else { |
||
85 | depth-- |
||
86 | posn = e.start + e.length |
||
87 | } |
||
88 | } else { |
||
89 | depth-- |
||
90 | posn = e.start + e.length |
||
91 | } |
||
92 | if (depth > 5) { return {start: -1} } |
||
93 | } |
||
94 | return e |
||
1 ignored issue
–
show
|
|||
95 | } |
||
96 | |||
97 | export function earlierOf (a, b) { |
||
98 | // inspects the .start property of a and b and returns the one |
||
99 | // with the lowest start position |
||
100 | if (b.start !== -1 && (a.start === -1 || b.start < a.start)) { |
||
101 | return b |
||
102 | } else { |
||
103 | return a |
||
104 | } |
||
105 | } |
||
106 | |||
107 | function _findMdpStartUnfenced (txt, start) { |
||
108 | let lookFrom = start |
||
109 | let m, c |
||
110 | while (true) { |
||
111 | m = _findMdpStart(txt, lookFrom) |
||
112 | if (m.start === -1) { return m } |
||
113 | c = findCode(txt, lookFrom) |
||
114 | if (c.start === -1 || m.start < c.start || m.start > (c.start + c.length)) { |
||
115 | // the mdp start we've found is not within a code fence |
||
116 | break |
||
117 | } |
||
118 | // the mdp start we've found is within a code fence so find the next one |
||
119 | lookFrom = c.start + c.length |
||
120 | } |
||
121 | return m |
||
1 ignored issue
–
show
|
|||
122 | |||
123 | function _findMdpStart (txt, start) { |
||
124 | let regex = /(\r\n|\n|^)([ ]{0,3}\[>[^\r\n\t\0[\]]*\]: # (\([^\r\n\t\0]*\)|"[^\r\n\t\0]*"|'[^\r\n\t\0]*'))(\r\n|\n)/g |
||
125 | regex.lastIndex = start |
||
126 | let regexResult = regex.exec(txt) |
||
127 | if (regexResult === null) { return {start: -1} } |
||
128 | let r = { |
||
129 | start: regexResult.index + regexResult[1].length, |
||
130 | internalStart: regexResult.index + regexResult[0].length, |
||
131 | commandString: regexResult[3].substring(1, regexResult[3].length - 1) |
||
132 | } |
||
133 | return r |
||
134 | } |
||
135 | } |
||
136 | |||
137 | function _findMdpEndUnfenced (txt, opening, start) { |
||
138 | let lookFrom = start |
||
139 | let m, c |
||
140 | while (true) { |
||
141 | m = _findMdpEnd(txt, opening, lookFrom - 2) |
||
142 | if (m.start === -1) { return m } |
||
143 | c = findCode(txt, lookFrom) |
||
144 | if (c.start === -1 || (m.internalStart + m.internalLength) < c.start || (m.internalStart + m.internalLength) > (c.start + c.length)) { break } // the mdp end we've found is not within a code fence |
||
145 | // the mdp end we've found is within a code fence so find the next one |
||
146 | lookFrom = c.start + c.length |
||
147 | } |
||
148 | return m |
||
1 ignored issue
–
show
|
|||
149 | |||
150 | function _findMdpEnd (txt, opening, start) { |
||
151 | let r = JSON.parse(JSON.stringify(opening)) // create copy of opening structure passed in |
||
152 | let regex = /(\r\n|\n)([ ]{0,3}\[<[^\r\n\t\0[\]]*\]: #)(\r\n|\n|$)/g |
||
153 | regex.lastIndex = start |
||
154 | let regexResult = regex.exec(txt) |
||
155 | if (regexResult === null) { return {start: -1} } |
||
156 | r.internalLength = regexResult.index - r.internalStart |
||
157 | r.length = regexResult.index + regexResult[0].length - regexResult[3].length - r.start |
||
158 | return r |
||
159 | } |
||
160 | } |
||
161 | |||
162 | function _findCodeSpan (txt, start) { |
||
163 | // finds an inline Code Span in the format: 'some text ``echo myfile.txt`` more text' |
||
164 | // look for start |
||
165 | let lookFrom = start |
||
166 | while (true) { |
||
167 | let s = _findCodeSpanStart(txt, lookFrom) |
||
168 | if (s.start === -1) { return s } |
||
169 | // look for end |
||
170 | let e = _findCodeSpanEnd(txt, s) |
||
171 | if (e.start !== -1) { return e } |
||
172 | lookFrom = s.internalStart |
||
173 | } |
||
174 | |||
175 | function _findCodeSpanStart (txt, start) { |
||
176 | let regex = /(^|[^`])(`+)[^`]/g |
||
177 | // 1st capture group is the first (or no) character prior to the identifying `'s |
||
178 | // 2nd group is the ` characters (however many there are) |
||
179 | regex.lastIndex = start |
||
180 | let regexResult = regex.exec(txt) |
||
181 | if (regexResult === null) { return {start: -1} } |
||
182 | let r = { |
||
183 | start: regexResult.index + regexResult[1].length, |
||
184 | internalStart: regexResult.index + regexResult[1].length + regexResult[2].length, |
||
185 | info: { |
||
186 | codeFence: regexResult[2] |
||
187 | } |
||
188 | } |
||
189 | return r |
||
190 | } |
||
191 | |||
192 | function _findCodeSpanEnd (txt, opening) { |
||
193 | let r = JSON.parse(JSON.stringify(opening)) // create copy of opening structure passed in |
||
194 | let regex = RegExp('([^`])(' + r.info.codeFence + ')($|[^`])', 'g') |
||
195 | regex.lastIndex = r.internalStart |
||
196 | let regexResult = regex.exec(txt) |
||
197 | if (regexResult === null) { return {start: -1} } |
||
198 | r.internalLength = regexResult.index + regexResult[1].length - r.internalStart |
||
199 | r.length = regexResult.index + regexResult[0].length - regexResult[3].length - r.start |
||
200 | r.commandString = '' |
||
201 | return r |
||
202 | } |
||
1 ignored issue
–
show
|
|||
203 | } |
||
204 | |||
205 | function _findIndentedCode (txt, start) { |
||
206 | let regex = /((?:^|\r\n|\n)[ ]{4,}[^\r\n\0]*){1,}/g |
||
207 | regex.lastIndex = start |
||
208 | let regexResult = regex.exec(txt) |
||
209 | if (regexResult === null) { |
||
210 | return {start: -1} |
||
211 | } else { |
||
212 | return { |
||
213 | start: regexResult.index, |
||
214 | length: regexResult[0].length, |
||
215 | internalStart: regexResult.index, |
||
216 | internalLength: regexResult[0].length, |
||
217 | info: {indent: regexResult[2]}, |
||
218 | commandString: '' |
||
219 | } |
||
220 | } |
||
221 | } |
||
222 | |||
223 | function _findFencedCode (txt, start) { |
||
224 | // if the internalLength is returned as -1 this means that text cannot simply be inserted at the internalStart |
||
225 | // location. Instead an additional preceding new line must be inserted along with the new text |
||
226 | // another way to look at this is that the internal text is 1 character short |
||
227 | // a value of -2 indicates a CRLF needs to be inserted |
||
228 | let a = _findOpeningCodeFence(txt, start) |
||
229 | if (a.start === -1) { return a } |
||
230 | return _findClosingCodeFence(txt, a) |
||
231 | |||
232 | function _findOpeningCodeFence (txt, start) { |
||
233 | // returns the location and type of the next opening code fence |
||
234 | let regex = /(^|\r\n|\n)([ ]{0,3}> |>|[ ]{0,0})(([ ]{0,3})([`]{3,}|[~]{3,})([^\n\r\0`]*))($|\r\n|\n)/g |
||
235 | /** The regex groups are: |
||
236 | * 0: the full match including any preamble block markup |
||
237 | * 1: the leading new line character(s) |
||
238 | * 2: the preamble consisting of block characters or nothing |
||
239 | * 3: the full codeFence line without preamble |
||
240 | * 4: any leading blank spaces at the start of the codeFence line |
||
241 | * 5: the ` or ~ characters identifying the codeFence |
||
242 | * 6: anything else on the line following the codeFence |
||
243 | * 7: the final new line character(s) |
||
244 | **/ |
||
245 | regex.lastIndex = start |
||
246 | let regexResult = regex.exec(txt) |
||
247 | if (regexResult === null) { |
||
248 | return {start: -1} |
||
249 | } |
||
250 | let r = { start: regexResult.index + regexResult[1].length, |
||
251 | info: { |
||
252 | blockQuote: regexResult[2], |
||
253 | spacesCount: regexResult[4].length, |
||
254 | codeFence: regexResult[5] |
||
255 | }, |
||
256 | commandString: regexResult[6].trim(), |
||
257 | internalStart: regexResult.index + regexResult[0].length |
||
258 | } |
||
259 | return r |
||
260 | } |
||
261 | |||
262 | function _findClosingCodeFence (txt, opening) { |
||
263 | // updates the passed result structure with the location and type of the next closing code fence |
||
264 | // to match the opening cofeFence passed in |
||
265 | let regex |
||
266 | let r = JSON.parse(JSON.stringify(opening)) // create copy of opening structure passed in |
||
267 | regex = RegExp('(^|\r\n|\n)([ ]{0,3}> |>|[ ]{0,0})[ ]{0,3}[' + r.info.codeFence[0] + ']{' + r.info.codeFence.length + ',}[ ]*($|\r\n|\n)', 'g') |
||
268 | regex.lastIndex = r.internalStart - 2 |
||
269 | let regexResult = regex.exec(txt) |
||
270 | if (opening.info.blockQuote.length !== 0) { |
||
271 | // we are in a block quote so the codeFence will end at the earlier of the found regex OR end of the block quote |
||
272 | let b = _findEndOfBlock(txt, r.internalStart) |
||
273 | if (b !== -1 && (regexResult === null || b < (regexResult.index + regexResult[1].length))) { |
||
274 | // the block end dictates the code block end |
||
275 | r.internalLength = b - r.internalStart |
||
276 | r.length = b - r.start |
||
277 | return r |
||
278 | } |
||
279 | } |
||
280 | if (regexResult === null) { |
||
281 | r.internalLength = txt.length - r.internalStart |
||
282 | r.length = txt.length - r.start |
||
283 | } else { |
||
284 | r.internalLength = regexResult.index - r.internalStart |
||
285 | r.length = regexResult.index + regexResult[0].length - regexResult[3].length - r.start |
||
286 | } |
||
287 | return r |
||
288 | } |
||
289 | |||
290 | function _findEndOfBlock (txt, start) { |
||
291 | // finds the first line which is not marked as block |
||
292 | let regex = /(\r\n|\n)(?!([ ]{0,3}> |>))[^>\r\n]*/g |
||
293 | regex.lastIndex = start |
||
294 | let regexResult = regex.exec(txt) |
||
295 | if (regexResult === null) { |
||
296 | return -1 |
||
297 | } else { |
||
298 | return regexResult.index |
||
299 | } |
||
300 | } |
||
301 | } |
||
302 |