llm-slop-detector 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/out/cli.js ADDED
@@ -0,0 +1,421 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const util_1 = require("util");
7
+ const rules_1 = require("./core/rules");
8
+ const types_1 = require("./core/types");
9
+ const scan_1 = require("./core/scan");
10
+ const ignore_1 = require("./core/ignore");
11
+ const PROSE_EXTENSIONS = new Map([
12
+ ['.md', 'markdown'],
13
+ ['.markdown', 'markdown'],
14
+ ['.mdown', 'markdown'],
15
+ ['.txt', 'plaintext'],
16
+ ['.text', 'plaintext'],
17
+ ]);
18
+ // Files git passes to commit-msg / prepare-commit-msg hooks, recognised by
19
+ // basename so `llm-slop .git/COMMIT_EDITMSG` works without a flag.
20
+ const GIT_MESSAGE_BASENAMES = new Map([
21
+ ['COMMIT_EDITMSG', 'git-commit'],
22
+ ['MERGE_MSG', 'git-commit'],
23
+ ['TAG_EDITMSG', 'git-commit'],
24
+ ['EDIT_DESCRIPTION', 'git-commit'],
25
+ ]);
26
+ const CODE_EXTENSIONS = new Map([
27
+ ['.ts', 'typescript'], ['.mts', 'typescript'], ['.cts', 'typescript'],
28
+ ['.tsx', 'typescriptreact'],
29
+ ['.js', 'javascript'], ['.mjs', 'javascript'], ['.cjs', 'javascript'],
30
+ ['.jsx', 'javascriptreact'],
31
+ ['.py', 'python'],
32
+ ['.rs', 'rust'],
33
+ ['.go', 'go'],
34
+ ['.java', 'java'],
35
+ ['.cs', 'csharp'],
36
+ ['.cpp', 'cpp'], ['.cxx', 'cpp'], ['.cc', 'cpp'], ['.hpp', 'cpp'], ['.hxx', 'cpp'],
37
+ ['.c', 'c'], ['.h', 'c'],
38
+ ['.rb', 'ruby'],
39
+ ['.php', 'php'],
40
+ ['.sh', 'shellscript'], ['.bash', 'shellscript'], ['.zsh', 'shellscript'],
41
+ ['.swift', 'swift'],
42
+ ['.kt', 'kotlin'], ['.kts', 'kotlin'],
43
+ ['.scala', 'scala'], ['.sc', 'scala'],
44
+ ['.dart', 'dart'],
45
+ ['.pl', 'perl'], ['.pm', 'perl'],
46
+ ['.r', 'r'],
47
+ ['.yaml', 'yaml'], ['.yml', 'yaml'],
48
+ ]);
49
+ const HELP = `llm-slop-detector [options] <paths...>
50
+
51
+ Scan markdown and plaintext files for LLM-style phrases and invisible Unicode.
52
+ With --scan-comments, also scan comments and docstrings in source code.
53
+
54
+ Options:
55
+ -f, --format <pretty|json|sarif> Output format (default: pretty)
56
+ --pack <name,...> Enable built-in rule packs
57
+ (${rules_1.BUILTIN_PACKS.join(', ')})
58
+ --no-builtin Skip the built-in core rule list
59
+ --config <path> Path to a .llmsloprc.json file
60
+ (default: nearest ancestor of cwd)
61
+ -s, --severity <level> Fail threshold: error | warning |
62
+ information | hint (default: information)
63
+ --scan-comments Scan comments/docstrings in source code
64
+ files (.ts, .py, .rs, .go, etc)
65
+ --exclude <pattern> .gitignore-style pattern to skip. Repeat
66
+ for multiple. Merged with .slopignore.
67
+ --no-slopignore Ignore the .slopignore file at cwd
68
+ --severity-override <k=v> Override severity for a selector. Repeat
69
+ for multiple. Value: error | warning |
70
+ information | hint | off.
71
+ Selectors: pack:<name>,
72
+ phrase:<pattern>, char:<literal|U+XXXX>,
73
+ source:<name>.
74
+ -q, --quiet Suppress the summary line
75
+ -h, --help Show this help
76
+ -v, --version Print version
77
+
78
+ Exit code: 0 if no findings at or above the severity threshold, 1 otherwise.
79
+
80
+ Examples:
81
+ llm-slop-detector README.md
82
+ llm-slop-detector --pack academic,cliches docs/
83
+ llm-slop-detector --format=json . > slop.json
84
+ llm-slop-detector --scan-comments src/
85
+ `;
86
+ function parseCli(argv) {
87
+ const parsed = (0, util_1.parseArgs)({
88
+ args: argv,
89
+ allowPositionals: true,
90
+ options: {
91
+ format: { type: 'string', short: 'f', default: 'pretty' },
92
+ pack: { type: 'string' },
93
+ 'no-builtin': { type: 'boolean', default: false },
94
+ config: { type: 'string' },
95
+ severity: { type: 'string', short: 's', default: 'information' },
96
+ 'scan-comments': { type: 'boolean', default: false },
97
+ exclude: { type: 'string', multiple: true, default: [] },
98
+ 'no-slopignore': { type: 'boolean', default: false },
99
+ 'severity-override': { type: 'string', multiple: true, default: [] },
100
+ quiet: { type: 'boolean', short: 'q', default: false },
101
+ help: { type: 'boolean', short: 'h', default: false },
102
+ version: { type: 'boolean', short: 'v', default: false },
103
+ },
104
+ strict: true,
105
+ });
106
+ if (parsed.values.help) {
107
+ process.stdout.write(HELP);
108
+ process.exit(0);
109
+ }
110
+ if (parsed.values.version) {
111
+ process.stdout.write(readPackageVersion() + '\n');
112
+ process.exit(0);
113
+ }
114
+ const format = parsed.values.format;
115
+ if (format !== 'pretty' && format !== 'json' && format !== 'sarif') {
116
+ die(`unknown --format: ${format}`);
117
+ }
118
+ const severityRaw = parsed.values.severity;
119
+ if (!(severityRaw in types_1.SEVERITY_RANK)) {
120
+ die(`unknown --severity: ${severityRaw}`);
121
+ }
122
+ const packsRaw = parsed.values.pack;
123
+ const packs = packsRaw ? packsRaw.split(',').map(s => s.trim()).filter(Boolean) : [];
124
+ for (const p of packs) {
125
+ if (!rules_1.BUILTIN_PACKS.includes(p)) {
126
+ die(`unknown pack: ${p}. Known: ${rules_1.BUILTIN_PACKS.join(', ')}`);
127
+ }
128
+ }
129
+ if (parsed.positionals.length === 0) {
130
+ die('no paths given. Try --help.');
131
+ }
132
+ const excludeRaw = parsed.values.exclude;
133
+ const exclude = Array.isArray(excludeRaw)
134
+ ? excludeRaw.filter((v) => typeof v === 'string')
135
+ : typeof excludeRaw === 'string' ? [excludeRaw] : [];
136
+ const overrideRaw = parsed.values['severity-override'];
137
+ const overrideSpecs = Array.isArray(overrideRaw)
138
+ ? overrideRaw.filter((v) => typeof v === 'string')
139
+ : typeof overrideRaw === 'string' ? [overrideRaw] : [];
140
+ const rawOverrides = {};
141
+ const validValues = new Set(['error', 'warning', 'information', 'info', 'hint', 'off']);
142
+ for (const spec of overrideSpecs) {
143
+ const eq = spec.indexOf('=');
144
+ if (eq === -1)
145
+ die(`invalid --severity-override: ${spec} (expected key=value)`);
146
+ const key = spec.slice(0, eq).trim();
147
+ const value = spec.slice(eq + 1).trim();
148
+ if (key.length === 0)
149
+ die(`invalid --severity-override: ${spec} (empty selector)`);
150
+ if (!validValues.has(value)) {
151
+ die(`invalid --severity-override value: ${spec} (expected error|warning|information|hint|off)`);
152
+ }
153
+ rawOverrides[key] = value;
154
+ }
155
+ const severityOverrides = (0, rules_1.parseSeverityOverrides)(rawOverrides);
156
+ return {
157
+ paths: parsed.positionals,
158
+ format: format,
159
+ packs,
160
+ useBuiltin: !parsed.values['no-builtin'],
161
+ configPath: parsed.values.config,
162
+ severity: severityRaw,
163
+ quiet: parsed.values.quiet,
164
+ scanComments: parsed.values['scan-comments'],
165
+ exclude,
166
+ noIgnoreFile: parsed.values['no-slopignore'],
167
+ severityOverrides,
168
+ };
169
+ }
170
+ function die(msg) {
171
+ process.stderr.write(`llm-slop: ${msg}\n`);
172
+ process.exit(2);
173
+ }
174
+ function readPackageVersion() {
175
+ try {
176
+ const pkgPath = path.resolve(__dirname, '..', 'package.json');
177
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
178
+ return pkg.version ?? '0.0.0';
179
+ }
180
+ catch {
181
+ return '0.0.0';
182
+ }
183
+ }
184
+ function extensionRoot() {
185
+ // Compiled CLI lives at out/cli.js; the extension root is its parent.
186
+ return path.resolve(__dirname, '..');
187
+ }
188
+ function collectFiles(paths, extensions, ignore, ignoreRoot) {
189
+ const result = [];
190
+ for (const p of paths) {
191
+ const abs = path.resolve(p);
192
+ let stat;
193
+ try {
194
+ stat = fs.statSync(abs);
195
+ }
196
+ catch {
197
+ process.stderr.write(`llm-slop: path not found: ${p}\n`);
198
+ continue;
199
+ }
200
+ if (stat.isFile()) {
201
+ const ext = path.extname(abs).toLowerCase();
202
+ const base = path.basename(abs);
203
+ if (extensions.has(ext) || GIT_MESSAGE_BASENAMES.has(base)) {
204
+ if (isIgnored(abs, ignoreRoot, ignore, false))
205
+ continue;
206
+ result.push(abs);
207
+ }
208
+ else {
209
+ process.stderr.write(`llm-slop: skipping ${p} (unrecognized extension; add --scan-comments for source code)\n`);
210
+ }
211
+ }
212
+ else if (stat.isDirectory()) {
213
+ walkDir(abs, extensions, result, ignore, ignoreRoot);
214
+ }
215
+ }
216
+ return result;
217
+ }
218
+ function isIgnored(absPath, ignoreRoot, ignore, isDirectory) {
219
+ if (ignore.patterns.length === 0)
220
+ return false;
221
+ const rel = path.relative(ignoreRoot, absPath);
222
+ if (rel.length === 0 || rel.startsWith('..'))
223
+ return false;
224
+ return ignore.ignores(rel, isDirectory);
225
+ }
226
+ function walkDir(dir, extensions, out, ignore, ignoreRoot) {
227
+ let entries;
228
+ try {
229
+ entries = fs.readdirSync(dir, { withFileTypes: true });
230
+ }
231
+ catch {
232
+ return;
233
+ }
234
+ for (const e of entries) {
235
+ if (e.name.startsWith('.') || e.name === 'node_modules' || e.name === 'out')
236
+ continue;
237
+ const full = path.join(dir, e.name);
238
+ if (e.isDirectory()) {
239
+ if (isIgnored(full, ignoreRoot, ignore, true))
240
+ continue;
241
+ walkDir(full, extensions, out, ignore, ignoreRoot);
242
+ }
243
+ else if (e.isFile() && extensions.has(path.extname(e.name).toLowerCase())) {
244
+ if (isIgnored(full, ignoreRoot, ignore, false))
245
+ continue;
246
+ out.push(full);
247
+ }
248
+ }
249
+ }
250
+ function languageFor(file, extensions) {
251
+ const byBasename = GIT_MESSAGE_BASENAMES.get(path.basename(file));
252
+ if (byBasename !== undefined)
253
+ return byBasename;
254
+ return extensions.get(path.extname(file).toLowerCase()) ?? 'plaintext';
255
+ }
256
+ function scanFile(file, rules, extensions) {
257
+ const text = fs.readFileSync(file, 'utf8');
258
+ return (0, scan_1.scanText)(text, rules, languageFor(file, extensions));
259
+ }
260
+ function shouldFail(reports, threshold) {
261
+ const thresholdRank = types_1.SEVERITY_RANK[threshold];
262
+ for (const r of reports) {
263
+ for (const f of r.findings) {
264
+ if (types_1.SEVERITY_RANK[f.severity] <= thresholdRank)
265
+ return true;
266
+ }
267
+ }
268
+ return false;
269
+ }
270
+ function formatPretty(reports, quiet) {
271
+ const lines = [];
272
+ let total = 0;
273
+ const counts = { error: 0, warning: 0, information: 0, hint: 0 };
274
+ const sevTag = { error: 'error', warning: 'warn', information: 'info', hint: 'hint' };
275
+ for (const r of reports) {
276
+ if (r.findings.length === 0)
277
+ continue;
278
+ const text = fs.readFileSync(r.path, 'utf8');
279
+ const rel = path.relative(process.cwd(), r.path) || r.path;
280
+ for (const f of r.findings) {
281
+ const { line, col } = (0, scan_1.offsetToLineCol)(text, f.offset);
282
+ lines.push(`${rel}:${line}:${col} ${sevTag[f.severity].padEnd(5)} ${f.message}`);
283
+ counts[f.severity]++;
284
+ total++;
285
+ }
286
+ }
287
+ if (!quiet) {
288
+ if (total === 0) {
289
+ lines.push('No slop found.');
290
+ }
291
+ else {
292
+ const parts = [];
293
+ for (const s of ['error', 'warning', 'information', 'hint']) {
294
+ if (counts[s] > 0)
295
+ parts.push(`${counts[s]} ${s}`);
296
+ }
297
+ lines.push('');
298
+ lines.push(`${total} finding${total === 1 ? '' : 's'} (${parts.join(', ')})`);
299
+ }
300
+ }
301
+ return lines.join('\n') + (lines.length > 0 ? '\n' : '');
302
+ }
303
+ function formatJson(reports) {
304
+ const out = [];
305
+ for (const r of reports) {
306
+ if (r.findings.length === 0)
307
+ continue;
308
+ const text = fs.readFileSync(r.path, 'utf8');
309
+ const rel = path.relative(process.cwd(), r.path) || r.path;
310
+ for (const f of r.findings) {
311
+ const start = (0, scan_1.offsetToLineCol)(text, f.offset);
312
+ const end = (0, scan_1.offsetToLineCol)(text, f.offset + f.length);
313
+ out.push({
314
+ path: rel,
315
+ line: start.line,
316
+ col: start.col,
317
+ endLine: end.line,
318
+ endCol: end.col,
319
+ code: f.code,
320
+ severity: f.severity,
321
+ message: f.message,
322
+ source: f.source,
323
+ rulePattern: f.rulePattern,
324
+ });
325
+ }
326
+ }
327
+ return JSON.stringify(out, null, 2) + '\n';
328
+ }
329
+ function formatSarif(reports, version) {
330
+ const sarifLevel = {
331
+ error: 'error',
332
+ warning: 'warning',
333
+ information: 'note',
334
+ hint: 'note',
335
+ };
336
+ const results = [];
337
+ for (const r of reports) {
338
+ if (r.findings.length === 0)
339
+ continue;
340
+ const text = fs.readFileSync(r.path, 'utf8');
341
+ const rel = path.relative(process.cwd(), r.path) || r.path;
342
+ for (const f of r.findings) {
343
+ const start = (0, scan_1.offsetToLineCol)(text, f.offset);
344
+ const end = (0, scan_1.offsetToLineCol)(text, f.offset + f.length);
345
+ results.push({
346
+ ruleId: f.code === 'char' ? `char:${f.matchText}` : `phrase:${f.rulePattern ?? f.matchText}`,
347
+ level: sarifLevel[f.severity],
348
+ message: { text: f.message },
349
+ locations: [{
350
+ physicalLocation: {
351
+ artifactLocation: { uri: rel },
352
+ region: {
353
+ startLine: start.line,
354
+ startColumn: start.col,
355
+ endLine: end.line,
356
+ endColumn: end.col,
357
+ },
358
+ },
359
+ }],
360
+ });
361
+ }
362
+ }
363
+ const sarif = {
364
+ $schema: 'https://json.schemastore.org/sarif-2.1.0.json',
365
+ version: '2.1.0',
366
+ runs: [{
367
+ tool: {
368
+ driver: {
369
+ name: 'llm-slop-detector',
370
+ version,
371
+ informationUri: 'https://github.com/mandakan/llm-slop-detector',
372
+ },
373
+ },
374
+ results,
375
+ }],
376
+ };
377
+ return JSON.stringify(sarif, null, 2) + '\n';
378
+ }
379
+ function main() {
380
+ const opts = parseCli(process.argv.slice(2));
381
+ const localRulePaths = [];
382
+ if (opts.configPath) {
383
+ if (!fs.existsSync(opts.configPath))
384
+ die(`--config not found: ${opts.configPath}`);
385
+ localRulePaths.push(path.resolve(opts.configPath));
386
+ }
387
+ else {
388
+ const found = (0, rules_1.findLocalRulePathFromCwd)(process.cwd());
389
+ if (found)
390
+ localRulePaths.push(found);
391
+ }
392
+ const rules = (0, rules_1.loadRules)({
393
+ extensionRoot: extensionRoot(),
394
+ useBuiltin: opts.useBuiltin,
395
+ enabledPacks: opts.packs,
396
+ localRulePaths,
397
+ userPhrases: [],
398
+ charReplacements: {},
399
+ severityOverrides: opts.severityOverrides,
400
+ });
401
+ const extensions = new Map(PROSE_EXTENSIONS);
402
+ if (opts.scanComments) {
403
+ for (const [k, v] of CODE_EXTENSIONS)
404
+ extensions.set(k, v);
405
+ }
406
+ const ignoreRoot = process.cwd();
407
+ const ignore = (0, ignore_1.loadIgnoreMatcher)(opts.noIgnoreFile ? null : ignoreRoot, opts.exclude);
408
+ const files = collectFiles(opts.paths, extensions, ignore, ignoreRoot);
409
+ const reports = files.map(f => ({ path: f, findings: scanFile(f, rules, extensions) }));
410
+ let output;
411
+ if (opts.format === 'json')
412
+ output = formatJson(reports);
413
+ else if (opts.format === 'sarif')
414
+ output = formatSarif(reports, readPackageVersion());
415
+ else
416
+ output = formatPretty(reports, opts.quiet);
417
+ process.stdout.write(output);
418
+ process.exit(shouldFail(reports, opts.severity) ? 1 : 0);
419
+ }
420
+ main();
421
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SUPPORTED_CODE_LANGUAGES = void 0;
4
+ exports.getCommentScanner = getCommentScanner;
5
+ const C_STYLE_LANGS = new Set([
6
+ 'typescript',
7
+ 'javascript',
8
+ 'typescriptreact',
9
+ 'javascriptreact',
10
+ 'rust',
11
+ 'go',
12
+ 'java',
13
+ 'csharp',
14
+ 'cpp',
15
+ 'c',
16
+ 'php',
17
+ 'swift',
18
+ 'kotlin',
19
+ 'scala',
20
+ 'dart',
21
+ ]);
22
+ const PYTHON_LANGS = new Set(['python']);
23
+ const HASH_LANGS = new Set(['ruby', 'shellscript', 'perl', 'r', 'yaml']);
24
+ exports.SUPPORTED_CODE_LANGUAGES = [
25
+ ...C_STYLE_LANGS,
26
+ ...PYTHON_LANGS,
27
+ ...HASH_LANGS,
28
+ ].sort();
29
+ function getCommentScanner(language) {
30
+ if (C_STYLE_LANGS.has(language))
31
+ return scanCStyleComments;
32
+ if (PYTHON_LANGS.has(language))
33
+ return scanPythonComments;
34
+ if (HASH_LANGS.has(language))
35
+ return scanHashComments;
36
+ return null;
37
+ }
38
+ // Extract // line comments and /* */ block comments, skipping contents of
39
+ // string literals (", ', `). Regex literals, division operators, and exotic
40
+ // lexical edge cases are not disambiguated -- acceptable given findings are
41
+ // Information severity and false positives are suppressible inline.
42
+ function scanCStyleComments(text) {
43
+ const ranges = [];
44
+ const n = text.length;
45
+ let i = 0;
46
+ let inString = null;
47
+ while (i < n) {
48
+ const c = text.charCodeAt(i);
49
+ if (inString !== null) {
50
+ if (c === 92 /* \ */ && i + 1 < n) {
51
+ i += 2;
52
+ continue;
53
+ }
54
+ if (text[i] === inString)
55
+ inString = null;
56
+ i++;
57
+ continue;
58
+ }
59
+ if (c === 47 /* / */ && i + 1 < n) {
60
+ const next = text.charCodeAt(i + 1);
61
+ if (next === 47 /* / */) {
62
+ const start = i;
63
+ const nl = text.indexOf('\n', i + 2);
64
+ const end = nl === -1 ? n : nl;
65
+ ranges.push([start, end]);
66
+ i = end;
67
+ continue;
68
+ }
69
+ if (next === 42 /* * */) {
70
+ const start = i;
71
+ const close = text.indexOf('*/', i + 2);
72
+ const end = close === -1 ? n : close + 2;
73
+ ranges.push([start, end]);
74
+ i = end;
75
+ continue;
76
+ }
77
+ }
78
+ if (c === 34 /* " */ || c === 39 /* ' */ || c === 96 /* ` */) {
79
+ inString = text[i];
80
+ i++;
81
+ continue;
82
+ }
83
+ i++;
84
+ }
85
+ return ranges;
86
+ }
87
+ // Extract # line comments and triple-quoted strings. Triple-quoted strings
88
+ // are scanned whether they're docstrings or raw data -- Option A can't
89
+ // distinguish without a parser, and the issue accepts this tradeoff.
90
+ function scanPythonComments(text) {
91
+ const ranges = [];
92
+ const n = text.length;
93
+ let i = 0;
94
+ let inString = null;
95
+ while (i < n) {
96
+ if (inString !== null) {
97
+ if (inString.triple) {
98
+ if (text.startsWith(inString.quote.repeat(3), i)) {
99
+ i += 3;
100
+ inString = null;
101
+ continue;
102
+ }
103
+ }
104
+ else {
105
+ if (text[i] === '\\' && i + 1 < n) {
106
+ i += 2;
107
+ continue;
108
+ }
109
+ if (text[i] === inString.quote || text[i] === '\n') {
110
+ inString = null;
111
+ }
112
+ }
113
+ i++;
114
+ continue;
115
+ }
116
+ if (text.startsWith('"""', i) || text.startsWith("'''", i)) {
117
+ const quote = text[i];
118
+ const start = i;
119
+ i += 3;
120
+ const closerIdx = text.indexOf(quote.repeat(3), i);
121
+ const end = closerIdx === -1 ? n : closerIdx + 3;
122
+ ranges.push([start, end]);
123
+ i = end;
124
+ continue;
125
+ }
126
+ if (text[i] === '#') {
127
+ const start = i;
128
+ const nl = text.indexOf('\n', i + 1);
129
+ const end = nl === -1 ? n : nl;
130
+ ranges.push([start, end]);
131
+ i = end;
132
+ continue;
133
+ }
134
+ if (text[i] === '"' || text[i] === "'") {
135
+ inString = { quote: text[i], triple: false };
136
+ i++;
137
+ continue;
138
+ }
139
+ i++;
140
+ }
141
+ return ranges;
142
+ }
143
+ // Extract # line comments, skipping contents of string literals.
144
+ // Works for Ruby, shell, Perl, R, YAML, etc. Heredocs and `$# ` parameter
145
+ // expansion are not specially handled.
146
+ function scanHashComments(text) {
147
+ const ranges = [];
148
+ const n = text.length;
149
+ let i = 0;
150
+ let inString = null;
151
+ while (i < n) {
152
+ if (inString !== null) {
153
+ if (text[i] === '\\' && i + 1 < n) {
154
+ i += 2;
155
+ continue;
156
+ }
157
+ if (text[i] === inString || text[i] === '\n')
158
+ inString = null;
159
+ i++;
160
+ continue;
161
+ }
162
+ if (text[i] === '#') {
163
+ const start = i;
164
+ const nl = text.indexOf('\n', i + 1);
165
+ const end = nl === -1 ? n : nl;
166
+ ranges.push([start, end]);
167
+ i = end;
168
+ continue;
169
+ }
170
+ if (text[i] === '"' || text[i] === "'") {
171
+ inString = text[i];
172
+ i++;
173
+ continue;
174
+ }
175
+ i++;
176
+ }
177
+ return ranges;
178
+ }
179
+ //# sourceMappingURL=comments.js.map