pi-hashline-edit-pro 4.4.4 → 4.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/README.md +44 -11
- package/index.ts +19 -4
- package/package.json +4 -3
- package/prompts/copy-guidelines.md +1 -0
- package/prompts/copy-snippet.md +1 -0
- package/prompts/copy.md +3 -0
- package/prompts/grep.md +1 -1
- package/prompts/insert.md +1 -1
- package/prompts/move-guidelines.md +1 -0
- package/prompts/move-snippet.md +1 -0
- package/prompts/move.md +3 -0
- package/prompts/replace-guidelines.md +0 -2
- package/prompts/replace.md +1 -1
- package/prompts/undo-last-change-guidelines.md +2 -1
- package/prompts/undo-last-change-snippet.md +1 -1
- package/prompts/undo-last-change.md +1 -1
- package/src/batch.ts +28 -6
- package/src/commit.ts +17 -4
- package/src/config-ui.ts +2 -1
- package/src/config.ts +6 -1
- package/src/constants.ts +1 -1
- package/src/copy-move.ts +793 -0
- package/src/edit-common.ts +19 -0
- package/src/file-reader.ts +4 -0
- package/src/hash-store/validation.ts +22 -0
- package/src/hash-store.ts +32 -4
- package/src/hashline/apply.ts +8 -3
- package/src/hashline/index.ts +3 -0
- package/src/hashline/parse.ts +22 -10
- package/src/hashline/resolve.ts +23 -8
- package/src/insert.ts +28 -10
- package/src/line-endings.ts +151 -0
- package/src/paths.ts +18 -2
- package/src/payload-contract.ts +29 -1
- package/src/replace-render.ts +4 -4
- package/src/replace-undo.ts +9 -2
- package/src/replace.ts +17 -5
- package/src/utils.ts +40 -5
package/src/paths.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
-
import { isAbsolute, relative, resolve as resolvePath, join, dirname } from "node:path";
|
|
2
|
+
import { isAbsolute, relative, resolve as resolvePath, join, dirname, parse } from "node:path";
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
function homeBase(): string {
|
|
@@ -55,6 +55,22 @@ export function toCwd(filePath: string, cwd: string): string {
|
|
|
55
55
|
return isAbsolute(expanded) ? expanded : resolvePath(cwd, expanded);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
function pathDepth(path: string): number {
|
|
59
|
+
const root = parse(path).root;
|
|
60
|
+
return path.slice(root.length).split(/[\\/]+/).filter((part) => part.length > 0).length;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function climbsToRoot(cwd: string, relativePath: string): boolean {
|
|
64
|
+
let climbs = 0;
|
|
65
|
+
for (const part of relativePath.split("/")) {
|
|
66
|
+
if (part !== "..") break;
|
|
67
|
+
climbs += 1;
|
|
68
|
+
}
|
|
69
|
+
return climbs > 0 && climbs >= pathDepth(resolvePath(cwd));
|
|
70
|
+
}
|
|
71
|
+
|
|
58
72
|
export function toDisplayPath(cwd: string, absolutePath: string, fallback?: string): string {
|
|
59
|
-
|
|
73
|
+
const rel = relative(cwd, absolutePath).replace(/\\/g, "/");
|
|
74
|
+
if (rel.length === 0 || climbsToRoot(cwd, rel)) return fallback ?? absolutePath;
|
|
75
|
+
return rel;
|
|
60
76
|
}
|
package/src/payload-contract.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { isRec, normalizeRequest, rejectUnknownFields, assertNoNul } from "./uti
|
|
|
4
4
|
const replacementLinesSchema = Type.Array(
|
|
5
5
|
Type.String({
|
|
6
6
|
description:
|
|
7
|
-
"One replacement line
|
|
7
|
+
"One replacement line. A real line break (\\n, \\r\\n, or \\r) splits it into lines and sets their endings; escape sequences are not decoded.",
|
|
8
8
|
}),
|
|
9
9
|
{
|
|
10
10
|
description:
|
|
@@ -131,3 +131,31 @@ export function assertInsertReq(request: unknown): asserts request is InsertReq
|
|
|
131
131
|
}
|
|
132
132
|
assertNoNul(request.lines);
|
|
133
133
|
}
|
|
134
|
+
|
|
135
|
+
const TRANSFER_KEYS = new Set(["path", "source_from", "source_to", "insert_after"]);
|
|
136
|
+
|
|
137
|
+
export interface TransferReq {
|
|
138
|
+
path?: string;
|
|
139
|
+
source_from: string;
|
|
140
|
+
source_to: string;
|
|
141
|
+
insert_after: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function assertTransferReq(request: unknown): asserts request is TransferReq {
|
|
145
|
+
if (!isRec(request)) {
|
|
146
|
+
throw new Error("[E_BAD_SHAPE] Copy/move request must be an object.");
|
|
147
|
+
}
|
|
148
|
+
rejectUnknownFields(request, TRANSFER_KEYS, "Copy/move request");
|
|
149
|
+
if (request.path !== undefined && typeof request.path !== "string") {
|
|
150
|
+
throw new Error('[E_BAD_SHAPE] Copy/move request field "path" must be a string when provided.');
|
|
151
|
+
}
|
|
152
|
+
if (typeof request.source_from !== "string" || request.source_from.length === 0) {
|
|
153
|
+
throw new Error('[E_BAD_SHAPE] Copy/move request requires a "source_from" string (4-char anchor from read output).');
|
|
154
|
+
}
|
|
155
|
+
if (typeof request.source_to !== "string" || request.source_to.length === 0) {
|
|
156
|
+
throw new Error('[E_BAD_SHAPE] Copy/move request requires a "source_to" string (4-char anchor from read output).');
|
|
157
|
+
}
|
|
158
|
+
if (typeof request.insert_after !== "string" || request.insert_after.length === 0) {
|
|
159
|
+
throw new Error('[E_BAD_SHAPE] Copy/move request requires an "insert_after" string (4-char anchor from read output).');
|
|
160
|
+
}
|
|
161
|
+
}
|
package/src/replace-render.ts
CHANGED
|
@@ -81,7 +81,7 @@ export function highlightBatchRefs(text: string, theme: FgT): string {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export function fmtCall(
|
|
84
|
-
args: { path?: string; remove_from?: string; remove_to?: string; anchor?: string } | undefined,
|
|
84
|
+
args: { path?: string; remove_from?: string; remove_to?: string; anchor?: string; source_from?: string; source_to?: string; insert_after?: string } | undefined,
|
|
85
85
|
state: RRState,
|
|
86
86
|
expanded: boolean,
|
|
87
87
|
theme: CallT,
|
|
@@ -89,7 +89,7 @@ export function fmtCall(
|
|
|
89
89
|
): string {
|
|
90
90
|
const previewPath = state.preview && "path" in state.preview ? state.preview.path : undefined;
|
|
91
91
|
const path = args?.path ?? state.resolvedPath ?? previewPath;
|
|
92
|
-
const anchorFallback = typeof args?.remove_from === "string" && typeof args?.remove_to === "string" ? `${args.remove_from}→${args.remove_to}` : typeof args?.anchor === "string" ? args.anchor : undefined;
|
|
92
|
+
const anchorFallback = typeof args?.remove_from === "string" && typeof args?.remove_to === "string" ? `${args.remove_from}→${args.remove_to}` : typeof args?.anchor === "string" ? args.anchor : typeof args?.source_from === "string" && typeof args?.insert_after === "string" ? `${args.source_from}→${args.insert_after}` : undefined;
|
|
93
93
|
const pathDisplay =
|
|
94
94
|
typeof path === "string" && path.length > 0
|
|
95
95
|
? theme.fg("accent", path)
|
|
@@ -264,9 +264,9 @@ export function reuseMarkdown(context: any, content: string, theme: any): Markdo
|
|
|
264
264
|
export function makeRenderCall(
|
|
265
265
|
preview: (args: unknown, cwd: string, signal?: AbortSignal) => Promise<RPreview>,
|
|
266
266
|
options: {
|
|
267
|
-
getInput?: (args: unknown) => { path?: string; remove_from?: string; remove_to?: string; anchor?: string } | null;
|
|
267
|
+
getInput?: (args: unknown) => { path?: string; remove_from?: string; remove_to?: string; anchor?: string; source_from?: string; source_to?: string; insert_after?: string } | null;
|
|
268
268
|
toolName?: string;
|
|
269
|
-
resolveTarget?: (input: { remove_from?: string; remove_to?: string; anchor?: string }) => string | undefined;
|
|
269
|
+
resolveTarget?: (input: { remove_from?: string; remove_to?: string; anchor?: string; source_from?: string; source_to?: string; insert_after?: string }) => string | undefined;
|
|
270
270
|
} = {},
|
|
271
271
|
) {
|
|
272
272
|
const getInput = options.getInput ?? getPreviewInput;
|
package/src/replace-undo.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { lineChecksum } from "./hashline";
|
|
|
9
9
|
import { freeAnchors, adoptAnchors, withAnchorSession, formatAnchorReclaimNotice, takeReclaimedPaths } from "./anchor-registry";
|
|
10
10
|
import { resolveInCwd, writeAtomic, type FileIdentity } from "./fs-write";
|
|
11
11
|
import { toLF, stripBOM, restoreEndings, type LineEnding } from "./normalize";
|
|
12
|
+
import { joinSeparators } from "./line-endings";
|
|
12
13
|
import { genDiff, genPatch, spansFromHashes } from "./replace-diff";
|
|
13
14
|
import { getDiffContextLines } from "./config";
|
|
14
15
|
import { cntDiff, errCode, makePrepareArguments, splitLines } from "./utils";
|
|
@@ -22,8 +23,10 @@ export interface UndoEntry {
|
|
|
22
23
|
content: string;
|
|
23
24
|
bom: string;
|
|
24
25
|
originalEnding: LineEnding;
|
|
26
|
+
separators?: LineEnding[];
|
|
25
27
|
hashes: string[];
|
|
26
28
|
resultContent: string;
|
|
29
|
+
resultSeparators?: LineEnding[];
|
|
27
30
|
mode?: number;
|
|
28
31
|
}
|
|
29
32
|
|
|
@@ -44,8 +47,10 @@ export async function saveUndo(
|
|
|
44
47
|
content: entry.content,
|
|
45
48
|
bom: entry.bom,
|
|
46
49
|
ending: entry.originalEnding,
|
|
50
|
+
...(entry.separators !== undefined ? { separators: entry.separators } : {}),
|
|
47
51
|
hashes: entry.hashes,
|
|
48
52
|
resultContent: entry.resultContent,
|
|
53
|
+
...(entry.resultSeparators !== undefined ? { resultSeparators: entry.resultSeparators } : {}),
|
|
49
54
|
...(mode !== undefined ? { mode } : {}),
|
|
50
55
|
});
|
|
51
56
|
} catch (error) {
|
|
@@ -80,8 +85,10 @@ export async function getUndo(path: string): Promise<UndoEntry | undefined> {
|
|
|
80
85
|
content: record.content,
|
|
81
86
|
bom: record.bom,
|
|
82
87
|
originalEnding,
|
|
88
|
+
...(record.separators !== undefined ? { separators: record.separators as LineEnding[] } : {}),
|
|
83
89
|
hashes: record.hashes,
|
|
84
90
|
resultContent: record.resultContent,
|
|
91
|
+
...(record.resultSeparators !== undefined ? { resultSeparators: record.resultSeparators as LineEnding[] } : {}),
|
|
85
92
|
...(record.mode !== undefined ? { mode: record.mode } : {}),
|
|
86
93
|
};
|
|
87
94
|
} catch (error) {
|
|
@@ -171,7 +178,7 @@ export function regUndo(pi: ExtensionAPI, flags: EditToolFlags = DEFAULT_EDIT_FL
|
|
|
171
178
|
|
|
172
179
|
if (
|
|
173
180
|
currentRaw !== undefined &&
|
|
174
|
-
currentRaw !== undo.bom + restoreEndings(undo.resultContent, undo.originalEnding)
|
|
181
|
+
currentRaw !== undo.bom + (undo.resultSeparators !== undefined ? joinSeparators(undo.resultContent, undo.resultSeparators) : restoreEndings(undo.resultContent, undo.originalEnding))
|
|
175
182
|
) {
|
|
176
183
|
return {
|
|
177
184
|
content: [
|
|
@@ -187,7 +194,7 @@ export function regUndo(pi: ExtensionAPI, flags: EditToolFlags = DEFAULT_EDIT_FL
|
|
|
187
194
|
|
|
188
195
|
await writeAtomic(
|
|
189
196
|
mutationTargetPath,
|
|
190
|
-
undo.bom + restoreEndings(undo.content, undo.originalEnding),
|
|
197
|
+
undo.bom + (undo.separators !== undefined ? joinSeparators(undo.content, undo.separators) : restoreEndings(undo.content, undo.originalEnding)),
|
|
191
198
|
currentIdentity,
|
|
192
199
|
undo.mode ?? fallbackFileMode(),
|
|
193
200
|
);
|
package/src/replace.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "./replace-diff";
|
|
11
11
|
import { readNormFile, type NormFile } from "./file-reader";
|
|
12
12
|
import { editToolSchema, buildEditToolSchema, type ReqParams, assertReq, normReq } from "./payload-contract";
|
|
13
|
-
import { splitLines } from "./utils";
|
|
13
|
+
import { literalEscapeWarning, splitLines } from "./utils";
|
|
14
14
|
import { loadP, loadGuide } from "./prompts";
|
|
15
15
|
import { type FileIdentity } from "./fs-write";
|
|
16
16
|
import { applyEdit,
|
|
@@ -21,6 +21,7 @@ import { applyEdit,
|
|
|
21
21
|
AnchorMismatchError,
|
|
22
22
|
type HEdit,
|
|
23
23
|
type NEdit,
|
|
24
|
+
type StripWarningLocation,
|
|
24
25
|
} from "./hashline";
|
|
25
26
|
import { type RMetrics } from "./replace-response";
|
|
26
27
|
import {
|
|
@@ -56,6 +57,7 @@ export interface PipelineResult {
|
|
|
56
57
|
result: string;
|
|
57
58
|
bom: string;
|
|
58
59
|
originalEnding: LineEnding;
|
|
60
|
+
originalSeparators: LineEnding[];
|
|
59
61
|
hadUtf8DecodeErrors: boolean;
|
|
60
62
|
warnings: string[];
|
|
61
63
|
noopEdit?: NEdit;
|
|
@@ -67,6 +69,7 @@ export interface PipelineResult {
|
|
|
67
69
|
totalRemovedLines: number;
|
|
68
70
|
identity: FileIdentity;
|
|
69
71
|
spans?: DiffSpan[];
|
|
72
|
+
contentSeparators?: (LineEnding | undefined)[];
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
|
|
@@ -76,6 +79,9 @@ export interface ExecPipelineOptions {
|
|
|
76
79
|
store?: HashStore;
|
|
77
80
|
noPersist?: boolean;
|
|
78
81
|
preloadedNorm?: NormFile;
|
|
82
|
+
served?: ReadonlyMap<string, string>;
|
|
83
|
+
allowEmpty?: boolean;
|
|
84
|
+
stripWarning?: StripWarningLocation;
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
export function hashSpan(hashes: string[], from: string, to: string): [number, number] | undefined {
|
|
@@ -91,7 +97,7 @@ export function spanForEdit(originalHashes: string[], from: string, to: string,
|
|
|
91
97
|
const replacementCount = splitLines(resultContent).length - (originalHashes.length - (span[1] - span[0] + 1));
|
|
92
98
|
return { start: span[0], end: span[1], replacementCount };
|
|
93
99
|
}
|
|
94
|
-
async function noteAnchorError(absolutePath: string, error: unknown, noPersist?: boolean): Promise<void> {
|
|
100
|
+
export async function noteAnchorError(absolutePath: string, error: unknown, noPersist?: boolean): Promise<void> {
|
|
95
101
|
if (noPersist === true) return;
|
|
96
102
|
if (error instanceof RangeStaleError) {
|
|
97
103
|
adoptAnchors(absolutePath, error.rangeServedMap);
|
|
@@ -137,8 +143,8 @@ export async function execPipeline(
|
|
|
137
143
|
const { edit, warnings: editWarnings } = buildReplaceHEdit(params);
|
|
138
144
|
const hashStore = options?.store ?? await loadHashStore();
|
|
139
145
|
const preResolvedPath = await resolveTarget(toCwd(targetPath, cwd));
|
|
140
|
-
const served = servedForPath(preResolvedPath);
|
|
141
|
-
const { normalized: originalNormalized, bom, originalEnding, fileHashes: originalHashes, hadUtf8DecodeErrors, absolutePath, identity } = await readNormFile(
|
|
146
|
+
const served = options?.served ?? servedForPath(preResolvedPath);
|
|
147
|
+
const { normalized: originalNormalized, bom, originalEnding, endingSeparators: originalSeparators, fileHashes: originalHashes, hadUtf8DecodeErrors, absolutePath, identity } = await readNormFile(
|
|
142
148
|
targetPath, cwd, { signal: options?.signal, accessMode: options?.accessMode, maxLines: MAX_HASH_LINES, store: hashStore, noPersist: options?.noPersist, allocation: options?.noPersist ? "shadow" : "real", preloadedNorm: options?.preloadedNorm },
|
|
143
149
|
);
|
|
144
150
|
const displayPath = toDisplayPath(cwd, absolutePath, targetPath);
|
|
@@ -152,6 +158,8 @@ export async function execPipeline(
|
|
|
152
158
|
originalHashes,
|
|
153
159
|
displayPath,
|
|
154
160
|
served,
|
|
161
|
+
options?.allowEmpty,
|
|
162
|
+
options?.stripWarning,
|
|
155
163
|
);
|
|
156
164
|
} catch (error) {
|
|
157
165
|
await noteAnchorError(absolutePath, error, options?.noPersist);
|
|
@@ -181,6 +189,7 @@ export async function execPipeline(
|
|
|
181
189
|
result,
|
|
182
190
|
bom,
|
|
183
191
|
originalEnding,
|
|
192
|
+
originalSeparators,
|
|
184
193
|
hadUtf8DecodeErrors,
|
|
185
194
|
warnings,
|
|
186
195
|
noopEdit: anchorResult.noopEdit,
|
|
@@ -192,6 +201,7 @@ export async function execPipeline(
|
|
|
192
201
|
totalRemovedLines,
|
|
193
202
|
identity,
|
|
194
203
|
...(pipeSpans ? { spans: pipeSpans } : {}),
|
|
204
|
+
...(edit.content_separators !== undefined ? { contentSeparators: edit.content_separators } : {}),
|
|
195
205
|
};
|
|
196
206
|
}
|
|
197
207
|
|
|
@@ -263,6 +273,7 @@ export function buildToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): ToolDef
|
|
|
263
273
|
const canonical = normReq(params);
|
|
264
274
|
assertReq(canonical);
|
|
265
275
|
const normalizedParams = canonical;
|
|
276
|
+
const literalEscape = literalEscapeWarning(normalizedParams.replacement_lines, "replacement_lines");
|
|
266
277
|
const targetPath = await resolveEditTargetWithRequirement({
|
|
267
278
|
removeFrom: normalizedParams.remove_from,
|
|
268
279
|
removeTo: normalizedParams.remove_to,
|
|
@@ -287,6 +298,7 @@ export function buildToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): ToolDef
|
|
|
287
298
|
absolutePath,
|
|
288
299
|
mutationTargetPath,
|
|
289
300
|
editAnchors: [normalizedParams.remove_from, normalizedParams.remove_to],
|
|
301
|
+
prefixWarnings: literalEscape !== undefined ? [literalEscape] : [],
|
|
290
302
|
signal,
|
|
291
303
|
});
|
|
292
304
|
}
|
|
@@ -305,7 +317,7 @@ export function buildToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): ToolDef
|
|
|
305
317
|
cwd: ctx.cwd,
|
|
306
318
|
signal,
|
|
307
319
|
hedit: built.edit,
|
|
308
|
-
extraWarnings: built.warnings,
|
|
320
|
+
extraWarnings: [...(literalEscape !== undefined ? [literalEscape] : []), ...built.warnings],
|
|
309
321
|
});
|
|
310
322
|
});
|
|
311
323
|
});
|
package/src/utils.ts
CHANGED
|
@@ -342,10 +342,6 @@ export function decodeStringArray(value: unknown, warnings?: string[], label = "
|
|
|
342
342
|
return undefined;
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
-
function splitEditLines(text: string): string[] {
|
|
346
|
-
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
347
|
-
}
|
|
348
|
-
|
|
349
345
|
function normalizeLineFieldValue(value: unknown): string[] | undefined {
|
|
350
346
|
const candidate = typeof value === "string"
|
|
351
347
|
? value
|
|
@@ -356,7 +352,7 @@ function normalizeLineFieldValue(value: unknown): string[] | undefined {
|
|
|
356
352
|
const decoded = decodeStringArray(candidate);
|
|
357
353
|
if (decoded !== undefined) return decoded;
|
|
358
354
|
if (/^\[\s*\]$/.test(stripTrailingMemberCall(stripCodeFence(candidate)))) return [];
|
|
359
|
-
return
|
|
355
|
+
return [candidate];
|
|
360
356
|
}
|
|
361
357
|
|
|
362
358
|
function normalizeEditLines(record: Record<string, unknown>): void {
|
|
@@ -366,3 +362,42 @@ function normalizeEditLines(record: Record<string, unknown>): void {
|
|
|
366
362
|
if (lines !== undefined) record[key] = lines;
|
|
367
363
|
}
|
|
368
364
|
}
|
|
365
|
+
|
|
366
|
+
const LITERAL_ESCAPE_RE = /\\(?:u([0-9a-fA-F]{4})|([ntr"]))/g;
|
|
367
|
+
|
|
368
|
+
const REAL_LINE_BREAK_RE = /[\n\r]/;
|
|
369
|
+
|
|
370
|
+
function isSurrogateEscapePair(line: string, index: number, hex: string): boolean {
|
|
371
|
+
const code = Number.parseInt(hex, 16);
|
|
372
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
373
|
+
const next = /^\\u([0-9a-fA-F]{4})/.exec(line.slice(index + 6))?.[1];
|
|
374
|
+
if (next === undefined) return false;
|
|
375
|
+
const nextCode = Number.parseInt(next, 16);
|
|
376
|
+
return nextCode >= 0xdc00 && nextCode <= 0xdfff;
|
|
377
|
+
}
|
|
378
|
+
if (code >= 0xdc00 && code <= 0xdfff) {
|
|
379
|
+
const previous = /\\u([0-9a-fA-F]{4})$/.exec(line.slice(0, index))?.[1];
|
|
380
|
+
if (previous === undefined) return false;
|
|
381
|
+
const previousCode = Number.parseInt(previous, 16);
|
|
382
|
+
return previousCode >= 0xd800 && previousCode <= 0xdbff;
|
|
383
|
+
}
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function literalEscapeWarning(lines: string[], label: string): string | undefined {
|
|
388
|
+
for (const line of lines) {
|
|
389
|
+
if (!line.includes("\\")) continue;
|
|
390
|
+
const hasRealBreak = REAL_LINE_BREAK_RE.test(line);
|
|
391
|
+
for (const match of line.matchAll(LITERAL_ESCAPE_RE)) {
|
|
392
|
+
const hex = match[1];
|
|
393
|
+
const simple = match[2];
|
|
394
|
+
if (simple !== undefined && hasRealBreak) continue;
|
|
395
|
+
if (hex !== undefined) {
|
|
396
|
+
if (hex.toLowerCase() === "dddd") continue;
|
|
397
|
+
if (isSurrogateEscapePair(line, match.index, hex)) continue;
|
|
398
|
+
}
|
|
399
|
+
return `[W_LITERAL_ESCAPE] "${label}" contains the literal escape text "${match[0]}"`;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return undefined;
|
|
403
|
+
}
|