mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-14 04:05:29 +02:00
- Read file-type filters as include and exclusion globs: rg -t/--type and the Grep tool's type include ripgrep 14.1's globs for the type, rg -T/--type-not exclude them, fd -e/--extension includes *.<ext>. An unknown type name, --type-add or --type-clear is an unreadable filter and never credits, like --exclude-from. - Treat a path pattern as a filter: the Glob tool's pattern must match the target below its root; a shell glob operand must match the target or a directory above it; a git pathspec matches the same way with its * also matching /. One operand that admits the target is enough. - Extend "a search of the target itself gives nothing" to searches confined to the target's own file name: when every include, the Glob pattern, or every path pattern ends in that exact name, the stem never credits, and the closest-miss note says it was a lookup. - Tokenize PowerShell searches with backslashes kept as path separators, as the read-only shell check already does; the old literal-prefix scope hid that src\core\*.ts reached the parser as srccore*.ts. - Make an edit non-trivial when either text holds U+2028, U+2029 or U+0085, which end a JavaScript or C# line comment; the lexer splits lines only at \n and \r. Extracting applyLongFilter, grepDetail and stemCredits leaves every touched function at or below its complexity on main.
565 lines
17 KiB
JavaScript
565 lines
17 KiB
JavaScript
'use strict';
|
|
|
|
// --- Code lines ---
|
|
// see docs/gateguard/change-profile.md#trivial-edits
|
|
|
|
function isSpace(ch) {
|
|
return ch === ' ' || ch === '\t' || ch === '\f' || ch === '\v';
|
|
}
|
|
|
|
function isIdentChar(ch) {
|
|
return ch !== undefined && ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch === '_' || ch === '$');
|
|
}
|
|
|
|
function isLetter(ch) {
|
|
return ch !== undefined && ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'));
|
|
}
|
|
|
|
function rustCharEnd(text, i) {
|
|
if (text[i + 1] === '\\') {
|
|
if (text[i + 2] === "'") return text[i + 3] === "'" ? i + 3 : -1;
|
|
const end = text.indexOf("'", i + 3);
|
|
return end !== -1 && end <= i + 12 ? end : -1;
|
|
}
|
|
const cp = text.codePointAt(i + 1);
|
|
if (cp === undefined || text[i + 1] === '\n' || text[i + 1] === '\r' || text[i + 1] === "'") return -1;
|
|
const end = i + (cp > 0xffff ? 3 : 2);
|
|
return text[end] === "'" ? end : -1;
|
|
}
|
|
|
|
function stringPrefixIsUnsafe(code, spec) {
|
|
const last = code[code.length - 1];
|
|
if (spec.rawPrefixes && last !== undefined && spec.rawPrefixes.includes(last)) return true;
|
|
if (!spec.fStrings || !isLetter(last)) return false;
|
|
const prefix = isLetter(code[code.length - 2]) ? code.slice(-2) : last;
|
|
return prefix.toLowerCase().includes('f');
|
|
}
|
|
|
|
// --- Directive comments ---
|
|
// see docs/gateguard/change-profile.md#directive-comments
|
|
|
|
const DIRECTIVE_LEADS = ['@', '#', '<', '+', '!', 'type:', 'global ', 'globals ', 'exported ', 'requires'];
|
|
const DIRECTIVE_RAW_LEADS = ['!', '/', 'go:', 'export ', 'extern ', 'line '];
|
|
const DIRECTIVE_WORDS = [
|
|
'lint', 'jshint', 'ts-', 'noqa', 'nosec', 'semgrep', 'sonar', 'noinspection', 'pragma', 'coding:', 'coding=', 'fmt:', 'isort:',
|
|
'mypy:', 'pyright:', 'pyre-', 'yapf:', 'ruff:', 'flake8', 'bandit', 'no cover', 'nocover', 'istanbul', 'c8 ', 'v8 ',
|
|
'prettier-', 'biome-', 'deno-', 'dprint-', 'rome-', 'webpack', 'vite-', '__pure__', '__no_side_effects__',
|
|
'__inline__', '@preserve', '@license', 'sourcemappingurl', 'sourceurl', '+build', 'cgo', 'clang-', 'cppcheck',
|
|
'coverity', 'iwyu', 'fallthrough', 'fall through', 'fall-through', 'fallthru', 'fall thru', 'shellcheck',
|
|
'psscriptanalyzer', 'suppress', 'jscs', 'checkstyle', 'spotbugs', 'findbugs', 'nopmd', 'codeql', 'lgtm', 'gitleaks',
|
|
'trufflehog', 'detect-secrets', 'allowlist', 'vim:', 'vi:', ' ex:', '-*-', 'code generated', 'do not edit',
|
|
'rubocop', 'swiftlint', 'resharper', '@ts-', '@type', '@typedef', '@template', '@satisfies', '@import', '@callback',
|
|
'@overload', '@enum', '@this', '@implements', '@extends', '@augments', '@jsx', '@flow', '@noflow', '@generated',
|
|
'compdef', 'autoload', 'output:'
|
|
];
|
|
|
|
function isDirectiveComment(body, spec) {
|
|
if (spec.rustDocs && (body[0] === '/' || body[0] === '!' || body[0] === '*')) return true;
|
|
if (DIRECTIVE_RAW_LEADS.some(lead => body.startsWith(lead))) return true;
|
|
const lower = body.toLowerCase();
|
|
let start = 0;
|
|
while (start < lower.length && (isSpace(lower[start]) || lower[start] === '*' || lower[start] === '/')) start++;
|
|
const lead = lower.slice(start);
|
|
if (DIRECTIVE_LEADS.some(word => lead.startsWith(word))) return true;
|
|
if (DIRECTIVE_WORDS.some(word => lower.includes(word))) return true;
|
|
return lower.includes('@') && lower.includes('{');
|
|
}
|
|
|
|
function directiveMarker(body, spec) {
|
|
return isDirectiveComment(body, spec) ? ` \u0001${Buffer.from(body, 'utf8').toString('hex')}\u0001 ` : '';
|
|
}
|
|
|
|
function endsWithContinuation(code) {
|
|
let end = code.length;
|
|
while (end > 0 && isSpace(code[end - 1])) end--;
|
|
return code[end - 1] === '\\';
|
|
}
|
|
|
|
const CODE_SPECIALS = new WeakMap();
|
|
|
|
function escapeClass(chars) {
|
|
return chars.replace(/[\\\]^-]/g, ch => `\\${ch}`);
|
|
}
|
|
|
|
function codeSpecials(spec) {
|
|
let specials = CODE_SPECIALS.get(spec);
|
|
if (!specials) {
|
|
const chars = `\n\r/${spec.lineComment === '#' ? '#' : ''}${spec.quotes || ''}${spec.forbiddenCode || ''}${spec.jsx !== undefined ? '<-' : ''}${spec.rustChars ? "'" : ''}`;
|
|
specials = { pattern: new RegExp(`[${escapeClass(chars)}]`, 'g'), chars: new Set(chars) };
|
|
CODE_SPECIALS.set(spec, specials);
|
|
}
|
|
return specials;
|
|
}
|
|
|
|
function nextSpecial(pattern, text, i) {
|
|
pattern.lastIndex = i;
|
|
const match = pattern.exec(text);
|
|
return match ? match.index : text.length;
|
|
}
|
|
|
|
const LINE_BREAK = /[\n\r]/g;
|
|
const BLOCK_SPECIALS = /[\n\r*/]/g;
|
|
const STRING_SPECIALS = new Map();
|
|
|
|
function stringSpecials(quote) {
|
|
let pattern = STRING_SPECIALS.get(quote);
|
|
if (!pattern) {
|
|
pattern = new RegExp(`[\\n\\r\\\\${escapeClass(quote)}]`, 'g');
|
|
STRING_SPECIALS.set(quote, pattern);
|
|
}
|
|
return pattern;
|
|
}
|
|
|
|
function lexCodeLines(text, spec) {
|
|
const specials = codeSpecials(spec);
|
|
const lines = [];
|
|
let code = '';
|
|
let state = 'code';
|
|
let quote = '';
|
|
let stringBody = '';
|
|
let commentStart = 0;
|
|
let tail = '';
|
|
const append = piece => {
|
|
code += piece;
|
|
tail = (tail + piece).slice(-2);
|
|
};
|
|
const endComment = end => {
|
|
const marker = directiveMarker(text.slice(commentStart, end), spec);
|
|
if (marker) append(marker);
|
|
};
|
|
const endLine = () => {
|
|
if (endsWithContinuation(code)) return false;
|
|
lines.push(code);
|
|
code = '';
|
|
tail = '';
|
|
return true;
|
|
};
|
|
if (spec.jsx !== undefined && text.startsWith('#!')) state = 'line';
|
|
for (let i = 0; i < text.length; i++) {
|
|
const ch = text[i];
|
|
if (ch === '\n' || ch === '\r') {
|
|
if (state === 'string') return null;
|
|
if (state === 'line') {
|
|
if (text.slice(commentStart, i).trimEnd().endsWith('\\')) return null;
|
|
endComment(i);
|
|
state = 'code';
|
|
}
|
|
if (state === 'block') {
|
|
lines.push(code);
|
|
code = '';
|
|
tail = '';
|
|
} else if (!endLine()) {
|
|
return null;
|
|
}
|
|
if (ch === '\r' && text[i + 1] === '\n') i++;
|
|
continue;
|
|
}
|
|
if (state === 'line') {
|
|
i = nextSpecial(LINE_BREAK, text, i) - 1;
|
|
continue;
|
|
}
|
|
if (state === 'block') {
|
|
if (ch !== '*' && ch !== '/') {
|
|
i = nextSpecial(BLOCK_SPECIALS, text, i + 1) - 1;
|
|
continue;
|
|
}
|
|
if (ch === '*' && text[i + 1] === '/') {
|
|
endComment(i);
|
|
state = 'code';
|
|
append(' ');
|
|
i++;
|
|
} else if (ch === '/' && text[i + 1] === '*') {
|
|
return null;
|
|
}
|
|
continue;
|
|
}
|
|
if (state === 'string') {
|
|
if (ch !== '\\' && ch !== quote) {
|
|
const end = nextSpecial(stringSpecials(quote), text, i + 1);
|
|
const run = text.slice(i, end);
|
|
append(run);
|
|
stringBody += run;
|
|
i = end - 1;
|
|
continue;
|
|
}
|
|
append(ch);
|
|
if (ch === '\\') {
|
|
const next = text[i + 1];
|
|
if (next === undefined || next === '\n' || next === '\r') return null;
|
|
append(next);
|
|
i++;
|
|
} else if (ch === quote) {
|
|
if (spec.noDollarInStrings && stringBody.includes('$')) return null;
|
|
state = 'code';
|
|
} else {
|
|
stringBody += ch;
|
|
}
|
|
continue;
|
|
}
|
|
if (!specials.chars.has(ch)) {
|
|
const end = nextSpecial(specials.pattern, text, i + 1);
|
|
append(text.slice(i, end));
|
|
i = end - 1;
|
|
continue;
|
|
}
|
|
if (spec.lineComment === '//' && ch === '/' && text[i + 1] === '/') {
|
|
state = 'line';
|
|
commentStart = i + 2;
|
|
i++;
|
|
continue;
|
|
}
|
|
if (spec.lineComment === '#' && ch === '#') {
|
|
state = 'line';
|
|
commentStart = i + 1;
|
|
continue;
|
|
}
|
|
if (spec.blockComment && ch === '/' && text[i + 1] === '*') {
|
|
state = 'block';
|
|
commentStart = i + 2;
|
|
i++;
|
|
continue;
|
|
}
|
|
if (spec.forbiddenCode && spec.forbiddenCode.includes(ch)) return null;
|
|
if (spec.jsx && ch === '<' && (isLetter(text[i + 1]) || text[i + 1] === '/' || text[i + 1] === '>' || text[i + 1] === '!')) return null;
|
|
if (spec.jsx !== undefined && ch === '-' && text[i + 1] === '-' && text[i + 2] === '>') return null;
|
|
if (ch === "'" && spec.rustChars) {
|
|
const end = rustCharEnd(text, i);
|
|
append(end === -1 ? ch : text.slice(i, end + 1));
|
|
if (end !== -1) i = end;
|
|
continue;
|
|
}
|
|
if (spec.quotes.includes(ch)) {
|
|
if (spec.tripleQuotes && text[i + 1] === ch && text[i + 2] === ch) return null;
|
|
if (stringPrefixIsUnsafe(tail, spec)) return null;
|
|
state = 'string';
|
|
quote = ch;
|
|
stringBody = '';
|
|
append(ch);
|
|
continue;
|
|
}
|
|
append(ch);
|
|
}
|
|
if (state === 'string' || state === 'block') return null;
|
|
if (state === 'line') {
|
|
if (text.slice(commentStart).trimEnd().endsWith('\\')) return null;
|
|
endComment(text.length);
|
|
}
|
|
return endLine() ? lines : null;
|
|
}
|
|
|
|
function normalizeCodeLine(line, spec) {
|
|
let out = '';
|
|
let pendingSpace = false;
|
|
let quote = '';
|
|
let indent = '';
|
|
let leading = true;
|
|
for (let i = 0; i < line.length; i++) {
|
|
const ch = line[i];
|
|
if (quote) {
|
|
out += ch;
|
|
if (ch === '\\' && i + 1 < line.length) out += line[++i];
|
|
else if (ch === quote) quote = '';
|
|
continue;
|
|
}
|
|
if (isSpace(ch)) {
|
|
if (leading) indent += ch;
|
|
else pendingSpace = true;
|
|
continue;
|
|
}
|
|
leading = false;
|
|
if (pendingSpace && out) out += ' ';
|
|
pendingSpace = false;
|
|
if (ch === "'" && spec.rustChars) {
|
|
const end = rustCharEnd(line, i);
|
|
out += end === -1 ? ch : line.slice(i, end + 1);
|
|
if (end !== -1) i = end;
|
|
continue;
|
|
}
|
|
if (spec.quotes.includes(ch)) quote = ch;
|
|
out += ch;
|
|
}
|
|
if (!out) return '';
|
|
return spec.indentSensitive ? `${indent}\u0000${out}` : out;
|
|
}
|
|
|
|
function hasSpaceAfterContinuation(line) {
|
|
let end = line.length;
|
|
while (end > 0 && isSpace(line[end - 1])) end--;
|
|
return end < line.length && line[end - 1] === '\\';
|
|
}
|
|
|
|
function codeSignature(text, spec) {
|
|
const lines = spec.lexer ? spec.lexer(text) : lexCodeLines(text, spec);
|
|
if (lines === null) return null;
|
|
const signature = [];
|
|
for (const line of lines) {
|
|
if (hasSpaceAfterContinuation(line)) return null;
|
|
const normalized = spec.normalize ? spec.normalize(line) : normalizeCodeLine(line, spec);
|
|
if (normalized === null) return null;
|
|
if (normalized) signature.push(normalized);
|
|
}
|
|
return signature;
|
|
}
|
|
|
|
// JavaScript ends a line at U+2028 and U+2029, and C# also at U+0085; the lexer splits only at \n and \r.
|
|
const UNICODE_LINE_TERMINATORS = /[\u0085\u2028\u2029]/;
|
|
|
|
function isTrivialEdit(oldString, newString, spec) {
|
|
if (UNICODE_LINE_TERMINATORS.test(oldString) || UNICODE_LINE_TERMINATORS.test(newString)) return false;
|
|
if (spec.lineComment === '//' && (oldString.includes('??/') || newString.includes('??/'))) return false;
|
|
const before = codeSignature(oldString, spec);
|
|
if (before === null) return false;
|
|
const after = codeSignature(newString, spec);
|
|
if (after === null || before.length !== after.length) return false;
|
|
return before.every((line, index) => line === after[index]);
|
|
}
|
|
|
|
|
|
// --- Shell lexing ---
|
|
// see docs/gateguard/change-profile.md#shell-scripts
|
|
|
|
const SHELL_SPEC = Object.freeze({});
|
|
|
|
function isLineStart(text, i) {
|
|
return i === 0 || text[i - 1] === '\n' || text[i - 1] === '\r';
|
|
}
|
|
|
|
function hashStartsComment(text, i) {
|
|
if (isLineStart(text, i)) return true;
|
|
const prev = text[i - 1];
|
|
if (prev === ' ' || prev === '\t' || prev === ';') return true;
|
|
return '()|&<>'.includes(prev) ? null : false;
|
|
}
|
|
|
|
function shDoubleQuoteEnd(text, i) {
|
|
for (let j = i + 1; j < text.length; j++) {
|
|
const ch = text[j];
|
|
if (ch === '"') return j;
|
|
if (ch === '\n' || ch === '\r' || ch === '`') return -1;
|
|
if (ch === '\\') {
|
|
if (j + 1 >= text.length || text[j + 1] === '\n' || text[j + 1] === '\r') return -1;
|
|
j++;
|
|
} else if (ch === '$' && text[j + 1] === '(') {
|
|
return -1;
|
|
} else if (ch === '$' && text[j + 1] === '{') {
|
|
const close = text.indexOf('}', j + 2);
|
|
if (close === -1) return -1;
|
|
for (let k = j + 2; k < close; k++) {
|
|
if ('"\'\n\r'.includes(text[k])) return -1;
|
|
}
|
|
j = close;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function hasLoneCarriageReturn(text) {
|
|
for (let at = text.indexOf('\r'); at !== -1; at = text.indexOf('\r', at + 1)) {
|
|
if (text[at + 1] !== '\n') return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isBlank(ch) {
|
|
return ch === ' ' || ch === '\t';
|
|
}
|
|
|
|
function shCodeLines(text) {
|
|
if (text.includes('<<') || text.includes('`') || hasLoneCarriageReturn(text)) return null;
|
|
const lines = [];
|
|
let code = '';
|
|
for (let i = 0; i < text.length; i++) {
|
|
const ch = text[i];
|
|
if (ch === '\n' || ch === '\r') {
|
|
lines.push(code);
|
|
code = '';
|
|
if (ch === '\r' && text[i + 1] === '\n') i++;
|
|
continue;
|
|
}
|
|
if (ch === '\\') {
|
|
const next = text[i + 1];
|
|
if (next === undefined || next === '\n' || next === '\r') return null;
|
|
code += ch + next;
|
|
i++;
|
|
continue;
|
|
}
|
|
if (ch === "'") {
|
|
if (text[i - 1] === '$') return null;
|
|
const end = text.indexOf("'", i + 1);
|
|
if (end === -1) return null;
|
|
const body = text.slice(i, end + 1);
|
|
if (body.includes('\n') || body.includes('\r')) return null;
|
|
code += body;
|
|
i = end;
|
|
continue;
|
|
}
|
|
if (ch === '"') {
|
|
const end = shDoubleQuoteEnd(text, i);
|
|
if (end === -1) return null;
|
|
code += text.slice(i, end + 1);
|
|
i = end;
|
|
continue;
|
|
}
|
|
if (ch === '#') {
|
|
const starts = hashStartsComment(text, i);
|
|
if (starts === null) return null;
|
|
if (starts) {
|
|
const start = i + 1;
|
|
while (i + 1 < text.length && text[i + 1] !== '\n' && text[i + 1] !== '\r') i++;
|
|
code += directiveMarker(text.slice(start, i + 1), SHELL_SPEC);
|
|
continue;
|
|
}
|
|
}
|
|
code += ch;
|
|
}
|
|
lines.push(code);
|
|
return lines;
|
|
}
|
|
|
|
const PS_SMART_QUOTES = /[\u2018\u2019\u201a\u201b\u201c\u201d\u201e]/;
|
|
|
|
function psHashStartsComment(text, i) {
|
|
if (isLineStart(text, i)) return true;
|
|
const prev = text[i - 1];
|
|
return prev === ' ' || prev === '\t' || prev === ';';
|
|
}
|
|
|
|
function psQuoteEnd(text, i) {
|
|
const quote = text[i];
|
|
for (let j = i + 1; j < text.length; j++) {
|
|
const ch = text[j];
|
|
if (ch === '\n' || ch === '\r') return -1;
|
|
if (quote === '"' && ch === '`') {
|
|
if (j + 1 >= text.length || text[j + 1] === '\n' || text[j + 1] === '\r') return -1;
|
|
j++;
|
|
} else if (quote === '"' && ch === '$' && text[j + 1] === '(') {
|
|
return -1;
|
|
} else if (ch === quote) {
|
|
if (text[j + 1] !== quote) return j;
|
|
j++;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function psCodeLines(text) {
|
|
if (text.includes('@"') || text.includes("@'") || PS_SMART_QUOTES.test(text)) return null;
|
|
const lines = [];
|
|
let code = '';
|
|
let block = false;
|
|
let blockStart = 0;
|
|
for (let i = 0; i < text.length; i++) {
|
|
const ch = text[i];
|
|
if (ch === '\n' || ch === '\r') {
|
|
lines.push(code);
|
|
code = '';
|
|
if (ch === '\r' && text[i + 1] === '\n') i++;
|
|
continue;
|
|
}
|
|
if (block) {
|
|
if (ch === '<' && text[i + 1] === '#') return null;
|
|
if (ch === '#' && text[i + 1] === '>') {
|
|
block = false;
|
|
code += directiveMarker(text.slice(blockStart, i), SHELL_SPEC) || ' ';
|
|
i++;
|
|
}
|
|
continue;
|
|
}
|
|
if (ch === '`') {
|
|
const next = text[i + 1];
|
|
if (next === undefined || next === '\n' || next === '\r') return null;
|
|
code += ch + next;
|
|
i++;
|
|
continue;
|
|
}
|
|
if (ch === "'" || ch === '"') {
|
|
const end = psQuoteEnd(text, i);
|
|
if (end === -1) return null;
|
|
code += text.slice(i, end + 1);
|
|
i = end;
|
|
continue;
|
|
}
|
|
if (ch === '<' && text[i + 1] === '#') {
|
|
if (!psHashStartsComment(text, i)) return null;
|
|
block = true;
|
|
blockStart = i + 2;
|
|
i++;
|
|
continue;
|
|
}
|
|
if (ch === '#') {
|
|
if (!psHashStartsComment(text, i)) return null;
|
|
const start = i + 1;
|
|
while (i + 1 < text.length && text[i + 1] !== '\n' && text[i + 1] !== '\r') i++;
|
|
code += directiveMarker(text.slice(start, i + 1), SHELL_SPEC);
|
|
continue;
|
|
}
|
|
code += ch;
|
|
}
|
|
if (block) return null;
|
|
lines.push(code);
|
|
return lines;
|
|
}
|
|
|
|
function batchCodeLines(text) {
|
|
const lines = [];
|
|
for (const line of text.split(/\r\n|\r|\n/)) {
|
|
if (line.endsWith('^')) return null;
|
|
let start = 0;
|
|
while (isBlank(line[start])) start++;
|
|
if (line[start] === '@') start++;
|
|
while (isBlank(line[start])) start++;
|
|
const word = line.slice(start, start + 3).toLowerCase();
|
|
const after = line[start + 3];
|
|
if (word === 'rem' && (after === undefined || isBlank(after))) {
|
|
if (/[%^&|<>()]/.test(line)) return null;
|
|
lines.push(directiveMarker(line.slice(start + 4), SHELL_SPEC));
|
|
} else {
|
|
lines.push(line.trim() ? line : '');
|
|
}
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
function normalizeShellLine(line, escape) {
|
|
let out = '';
|
|
let pendingSpace = false;
|
|
for (let i = 0; i < line.length; i++) {
|
|
const ch = line[i];
|
|
if (isBlank(ch)) {
|
|
pendingSpace = true;
|
|
continue;
|
|
}
|
|
if (pendingSpace && out) out += ' ';
|
|
pendingSpace = false;
|
|
if (ch === escape && i + 1 < line.length) {
|
|
out += ch + line[++i];
|
|
} else if (ch === "'" || ch === '"') {
|
|
const end = escape === '`' ? psQuoteEnd(line, i) : ch === "'" ? line.indexOf("'", i + 1) : shDoubleQuoteEnd(line, i);
|
|
if (end === -1) return null;
|
|
out += line.slice(i, end + 1);
|
|
i = end;
|
|
} else {
|
|
out += ch;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
|
|
module.exports = {
|
|
isSpace,
|
|
isIdentChar,
|
|
isLetter,
|
|
rustCharEnd,
|
|
isBlank,
|
|
hashStartsComment,
|
|
hasLoneCarriageReturn,
|
|
PS_SMART_QUOTES,
|
|
psHashStartsComment,
|
|
isTrivialEdit,
|
|
shCodeLines,
|
|
psCodeLines,
|
|
batchCodeLines,
|
|
normalizeShellLine
|
|
};
|