UNPKG

12.8 kBPlain TextView Raw
1/*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31import * as Platform from '../../core/platform/platform.js';
32
33import {SearchMatch} from './ContentProvider.js';
34import {Text} from './Text.js';
35
36export const Utils = {
37 // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
38 // eslint-disable-next-line @typescript-eslint/naming-convention
39 get _keyValueFilterRegex(): RegExp {
40 return /(?:^|\s)(\-)?([\w\-]+):([^\s]+)/;
41 },
42 // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
43 // eslint-disable-next-line @typescript-eslint/naming-convention
44 get _regexFilterRegex(): RegExp {
45 return /(?:^|\s)(\-)?\/([^\s]+)\//;
46 },
47 // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
48 // eslint-disable-next-line @typescript-eslint/naming-convention
49 get _textFilterRegex(): RegExp {
50 return /(?:^|\s)(\-)?([^\s]+)/;
51 },
52 // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
53 // eslint-disable-next-line @typescript-eslint/naming-convention
54 get _SpaceCharRegex(): RegExp {
55 return /\s/;
56 },
57 /**
58 * @enum {string}
59 */
60 // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
61 // eslint-disable-next-line @typescript-eslint/naming-convention
62 get Indent(): {TwoSpaces: ' ', FourSpaces: ' ', EightSpaces: ' ', TabCharacter: '\t'} {
63 return {TwoSpaces: ' ', FourSpaces: ' ', EightSpaces: ' ', TabCharacter: '\t'};
64 },
65
66 isStopChar: function(char: string): boolean {
67 return (char > ' ' && char < '0') || (char > '9' && char < 'A') || (char > 'Z' && char < '_') ||
68 (char > '_' && char < 'a') || (char > 'z' && char <= '~');
69 },
70
71 isWordChar: function(char: string): boolean {
72 return !Utils.isStopChar(char) && !Utils.isSpaceChar(char);
73 },
74
75 isSpaceChar: function(char: string): boolean {
76 return Utils._SpaceCharRegex.test(char);
77 },
78
79 isWord: function(word: string): boolean {
80 for (let i = 0; i < word.length; ++i) {
81 if (!Utils.isWordChar(word.charAt(i))) {
82 return false;
83 }
84 }
85 return true;
86 },
87
88 isOpeningBraceChar: function(char: string): boolean {
89 return char === '(' || char === '{';
90 },
91
92 isClosingBraceChar: function(char: string): boolean {
93 return char === ')' || char === '}';
94 },
95
96 isBraceChar: function(char: string): boolean {
97 return Utils.isOpeningBraceChar(char) || Utils.isClosingBraceChar(char);
98 },
99
100 textToWords: function(text: string, isWordChar: (arg0: string) => boolean, wordCallback: (arg0: string) => void):
101 void {
102 let startWord = -1;
103 for (let i = 0; i < text.length; ++i) {
104 if (!isWordChar(text.charAt(i))) {
105 if (startWord !== -1) {
106 wordCallback(text.substring(startWord, i));
107 }
108 startWord = -1;
109 } else if (startWord === -1) {
110 startWord = i;
111 }
112 }
113 if (startWord !== -1) {
114 wordCallback(text.substring(startWord));
115 }
116 },
117
118 lineIndent: function(line: string): string {
119 let indentation = 0;
120 while (indentation < line.length && Utils.isSpaceChar(line.charAt(indentation))) {
121 ++indentation;
122 }
123 return line.substr(0, indentation);
124 },
125
126 isUpperCase: function(text: string): boolean {
127 return text === text.toUpperCase();
128 },
129
130 isLowerCase: function(text: string): boolean {
131 return text === text.toLowerCase();
132 },
133
134 splitStringByRegexes(text: string, regexes: RegExp[]): {
135 value: string,
136 position: number,
137 regexIndex: number,
138 captureGroups: Array<string|undefined>,
139 }[] {
140 const matches: {
141 value: string,
142 position: number,
143 regexIndex: number,
144 captureGroups: (string|undefined)[],
145 }[] = [];
146 const globalRegexes: RegExp[] = [];
147 for (let i = 0; i < regexes.length; i++) {
148 const regex = regexes[i];
149 if (!regex.global) {
150 globalRegexes.push(new RegExp(regex.source, regex.flags ? regex.flags + 'g' : 'g'));
151 } else {
152 globalRegexes.push(regex);
153 }
154 }
155 doSplit(text, 0, 0);
156 return matches;
157
158 function doSplit(text: string, regexIndex: number, startIndex: number): void {
159 if (regexIndex >= globalRegexes.length) {
160 // Set regexIndex as -1 if text did not match with any regular expression
161 matches.push({value: text, position: startIndex, regexIndex: -1, captureGroups: []});
162 return;
163 }
164 const regex = globalRegexes[regexIndex];
165 let currentIndex = 0;
166 let result;
167 regex.lastIndex = 0;
168 while ((result = regex.exec(text)) !== null) {
169 const stringBeforeMatch = text.substring(currentIndex, result.index);
170 if (stringBeforeMatch) {
171 doSplit(stringBeforeMatch, regexIndex + 1, startIndex + currentIndex);
172 }
173 const match = result[0];
174 matches.push({
175 value: match,
176 position: startIndex + result.index,
177 regexIndex: regexIndex,
178 captureGroups: result.slice(1),
179 });
180 currentIndex = result.index + match.length;
181 }
182 const stringAfterMatches = text.substring(currentIndex);
183 if (stringAfterMatches) {
184 doSplit(stringAfterMatches, regexIndex + 1, startIndex + currentIndex);
185 }
186 }
187 },
188};
189
190export class FilterParser {
191 private readonly keys: string[];
192 constructor(keys: string[]) {
193 this.keys = keys;
194 }
195
196 static cloneFilter(filter: ParsedFilter): ParsedFilter {
197 return {key: filter.key, text: filter.text, regex: filter.regex, negative: filter.negative};
198 }
199
200 parse(query: string): ParsedFilter[] {
201 const splitFilters = Utils.splitStringByRegexes(
202 query, [Utils._keyValueFilterRegex, Utils._regexFilterRegex, Utils._textFilterRegex]);
203 const parsedFilters: ParsedFilter[] = [];
204 for (const {regexIndex, captureGroups} of splitFilters) {
205 if (regexIndex === -1) {
206 continue;
207 }
208 if (regexIndex === 0) {
209 const startsWithMinus = captureGroups[0];
210 const parsedKey = captureGroups[1];
211 const parsedValue = captureGroups[2];
212 if (this.keys.indexOf((parsedKey as string)) !== -1) {
213 parsedFilters.push({
214 key: parsedKey,
215 regex: undefined,
216 text: parsedValue,
217 negative: Boolean(startsWithMinus),
218 });
219 } else {
220 parsedFilters.push({
221 key: undefined,
222 regex: undefined,
223 text: `${parsedKey}:${parsedValue}`,
224 negative: Boolean(startsWithMinus),
225 });
226 }
227 } else if (regexIndex === 1) {
228 const startsWithMinus = captureGroups[0];
229 const parsedRegex = captureGroups[1];
230 try {
231 parsedFilters.push({
232 key: undefined,
233 regex: new RegExp((parsedRegex as string), 'i'),
234 text: undefined,
235 negative: Boolean(startsWithMinus),
236 });
237 } catch (e) {
238 parsedFilters.push({
239 key: undefined,
240 regex: undefined,
241 text: `/${parsedRegex}/`,
242 negative: Boolean(startsWithMinus),
243 });
244 }
245 } else if (regexIndex === 2) {
246 const startsWithMinus = captureGroups[0];
247 const parsedText = captureGroups[1];
248 parsedFilters.push({
249 key: undefined,
250 regex: undefined,
251 text: parsedText,
252 negative: Boolean(startsWithMinus),
253 });
254 }
255 }
256 return parsedFilters;
257 }
258}
259
260export class BalancedJSONTokenizer {
261 private readonly callback: (arg0: string) => void;
262 private index: number;
263 private balance: number;
264 private buffer: string;
265 private findMultiple: boolean;
266 private closingDoubleQuoteRegex: RegExp;
267 private lastBalancedIndex?: number;
268 constructor(callback: (arg0: string) => void, findMultiple?: boolean) {
269 this.callback = callback;
270 this.index = 0;
271 this.balance = 0;
272 this.buffer = '';
273 this.findMultiple = findMultiple || false;
274 this.closingDoubleQuoteRegex = /[^\\](?:\\\\)*"/g;
275 }
276
277 write(chunk: string): boolean {
278 this.buffer += chunk;
279 const lastIndex = this.buffer.length;
280 const buffer = this.buffer;
281 let index;
282 for (index = this.index; index < lastIndex; ++index) {
283 const character = buffer[index];
284 if (character === '"') {
285 this.closingDoubleQuoteRegex.lastIndex = index;
286 if (!this.closingDoubleQuoteRegex.test(buffer)) {
287 break;
288 }
289 index = this.closingDoubleQuoteRegex.lastIndex - 1;
290 } else if (character === '{') {
291 ++this.balance;
292 } else if (character === '}') {
293 --this.balance;
294 if (this.balance < 0) {
295 this.reportBalanced();
296 return false;
297 }
298 if (!this.balance) {
299 this.lastBalancedIndex = index + 1;
300 if (!this.findMultiple) {
301 break;
302 }
303 }
304 } else if (character === ']' && !this.balance) {
305 this.reportBalanced();
306 return false;
307 }
308 }
309 this.index = index;
310 this.reportBalanced();
311 return true;
312 }
313
314 private reportBalanced(): void {
315 if (!this.lastBalancedIndex) {
316 return;
317 }
318 this.callback(this.buffer.slice(0, this.lastBalancedIndex));
319 this.buffer = this.buffer.slice(this.lastBalancedIndex);
320 this.index -= this.lastBalancedIndex;
321 this.lastBalancedIndex = 0;
322 }
323
324 remainder(): string {
325 return this.buffer;
326 }
327}
328
329export interface TokenizerFactory {
330 // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
331 // eslint-disable-next-line @typescript-eslint/no-explicit-any
332 createTokenizer(mimeType: string):
333 (arg0: string, arg1: (arg0: string, arg1: string|null, arg2: number, arg3: number) => void) => void;
334}
335
336export function isMinified(text: string): boolean {
337 const kMaxNonMinifiedLength = 500;
338 let linesToCheck = 10;
339 let lastPosition = 0;
340 do {
341 let eolIndex = text.indexOf('\n', lastPosition);
342 if (eolIndex < 0) {
343 eolIndex = text.length;
344 }
345 if (eolIndex - lastPosition > kMaxNonMinifiedLength && text.substr(lastPosition, 3) !== '//#') {
346 return true;
347 }
348 lastPosition = eolIndex + 1;
349 } while (--linesToCheck >= 0 && lastPosition < text.length);
350
351 // Check the end of the text as well
352 linesToCheck = 10;
353 lastPosition = text.length;
354 do {
355 let eolIndex = text.lastIndexOf('\n', lastPosition);
356 if (eolIndex < 0) {
357 eolIndex = 0;
358 }
359 if (lastPosition - eolIndex > kMaxNonMinifiedLength && text.substr(lastPosition, 3) !== '//#') {
360 return true;
361 }
362 lastPosition = eolIndex - 1;
363 } while (--linesToCheck >= 0 && lastPosition > 0);
364 return false;
365}
366
367export const performSearchInContent = function(
368 content: string, query: string, caseSensitive: boolean, isRegex: boolean): SearchMatch[] {
369 const regex = Platform.StringUtilities.createSearchRegex(query, caseSensitive, isRegex);
370
371 const text = new Text(content);
372 const result = [];
373 for (let i = 0; i < text.lineCount(); ++i) {
374 const lineContent = text.lineAt(i);
375 regex.lastIndex = 0;
376 const match = regex.exec(lineContent);
377 if (match) {
378 result.push(new SearchMatch(i, lineContent, match.index));
379 }
380 }
381 return result;
382};
383export interface ParsedFilter {
384 key?: string;
385 text?: string|null;
386 regex?: RegExp;
387 negative: boolean;
388}