Skip to content

Commit 3420c5e

Browse files
committed
fixing order check
1 parent 52a252a commit 3420c5e

File tree

2 files changed

+179
-11
lines changed

2 files changed

+179
-11
lines changed

scripts/check-redirects-on-rename.spec.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,93 @@ const userDocsRedirects = [
145145
expect(result.userDocsRedirects[0].source).toBe('/platforms/old/path/');
146146
expect(result.userDocsRedirects[0].destination).toBe('/platforms/new/path/');
147147
});
148+
149+
it('should handle property order flexibility (destination before source)', () => {
150+
// Test that the parser works even when destination comes before source
151+
const redirectsWithReversedOrder = `
152+
const developerDocsRedirects = [
153+
{
154+
destination: '/sdk/new-path/',
155+
source: '/sdk/old-path/',
156+
},
157+
];
158+
159+
const userDocsRedirects = [
160+
{
161+
destination: '/platforms/javascript/new-guide/',
162+
source: '/platforms/javascript/old-guide/',
163+
},
164+
];
165+
`;
166+
fs.writeFileSync(tempFile, redirectsWithReversedOrder);
167+
const result = parseRedirectsJs(tempFile);
168+
169+
expect(result.developerDocsRedirects).toHaveLength(1);
170+
expect(result.developerDocsRedirects[0].source).toBe('/sdk/old-path/');
171+
expect(result.developerDocsRedirects[0].destination).toBe('/sdk/new-path/');
172+
173+
expect(result.userDocsRedirects).toHaveLength(1);
174+
expect(result.userDocsRedirects[0].source).toBe('/platforms/javascript/old-guide/');
175+
expect(result.userDocsRedirects[0].destination).toBe('/platforms/javascript/new-guide/');
176+
});
177+
178+
it('should handle escaped quotes in URLs', () => {
179+
// Test that URLs containing escaped quotes are parsed correctly
180+
// In JavaScript strings, \" represents a literal quote character
181+
// Note: In template literals (backticks), \" is NOT escaped - it's just \ + "
182+
// So we write it as \\" in the template to get \" in the actual string
183+
const redirectsWithEscapedQuotes = `
184+
const developerDocsRedirects = [
185+
{
186+
source: '/sdk/path/with\\"quotes/',
187+
destination: '/sdk/new-path/',
188+
},
189+
];
190+
191+
const userDocsRedirects = [
192+
{
193+
source: '/platforms/javascript/guide\\"test/',
194+
destination: '/platforms/javascript/new-guide/',
195+
},
196+
];
197+
`;
198+
fs.writeFileSync(tempFile, redirectsWithEscapedQuotes);
199+
const result = parseRedirectsJs(tempFile);
200+
201+
expect(result.developerDocsRedirects).toHaveLength(1);
202+
// The string contains \" (backslash + quote), which is preserved as-is
203+
expect(result.developerDocsRedirects[0].source).toBe('/sdk/path/with\\"quotes/');
204+
expect(result.developerDocsRedirects[0].destination).toBe('/sdk/new-path/');
205+
206+
expect(result.userDocsRedirects).toHaveLength(1);
207+
expect(result.userDocsRedirects[0].source).toBe('/platforms/javascript/guide\\"test/');
208+
expect(result.userDocsRedirects[0].destination).toBe('/platforms/javascript/new-guide/');
209+
});
210+
211+
it('should handle escaped backslashes before quotes', () => {
212+
// Test that \\" (escaped backslash + quote) in double-quoted strings is handled correctly
213+
// In a double-quoted JavaScript string, \\" means:
214+
// \\ escapes to a single literal backslash
215+
// \" escapes to a literal quote
216+
// So the string value contains \" (backslash + quote)
217+
const redirectsWithEscapedBackslashQuote = `
218+
const developerDocsRedirects = [
219+
{
220+
source: "/sdk/path/with\\"quotes/",
221+
destination: '/sdk/new-path/',
222+
},
223+
];
224+
`;
225+
fs.writeFileSync(tempFile, redirectsWithEscapedBackslashQuote);
226+
const result = parseRedirectsJs(tempFile);
227+
228+
expect(result.developerDocsRedirects).toHaveLength(1);
229+
// The string contains \" which should be parsed correctly
230+
// When we read the file, \" is two characters: \ and "
231+
// Our parser should handle this and include both in the value
232+
expect(result.developerDocsRedirects[0].source).toBe('/sdk/path/with\\"quotes/');
233+
expect(result.developerDocsRedirects[0].destination).toBe('/sdk/new-path/');
234+
});
148235
});
149236

150237
describe('redirectMatches', () => {

scripts/check-redirects-on-rename.ts

Lines changed: 92 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -251,32 +251,113 @@ function parseRedirectsJs(filePath: string): {
251251
return {developerDocsRedirects, userDocsRedirects};
252252
}
253253

254+
/**
255+
* Extracts a string value from a JavaScript string literal, handling escaped quotes
256+
* Supports both single and double quotes
257+
*/
258+
function extractStringValue(
259+
content: string,
260+
startIndex: number
261+
): {endIndex: number; value: string} | null {
262+
const quoteChar = content[startIndex];
263+
if (quoteChar !== '"' && quoteChar !== "'") {
264+
return null;
265+
}
266+
267+
let value = '';
268+
let i = startIndex + 1; // Start after the opening quote
269+
270+
while (i < content.length) {
271+
const char = content[i];
272+
273+
if (char === '\\') {
274+
// Handle escaped characters
275+
if (i + 1 < content.length) {
276+
const nextChar = content[i + 1];
277+
278+
// Special case: \\" should be parsed as \" (escaped quote)
279+
// In JavaScript: \\ escapes the backslash, \" escapes the quote
280+
// So \\" becomes \" (backslash + quote) in the string value
281+
if (nextChar === '\\' && i + 2 < content.length && content[i + 2] === quoteChar) {
282+
// This is \\" which should be parsed as \" (escaped quote)
283+
value += '\\' + quoteChar;
284+
i += 3;
285+
continue;
286+
}
287+
288+
// Handle escaped quote, backslash, and other escape sequences
289+
if (nextChar === quoteChar || nextChar === '\\') {
290+
value += char + nextChar;
291+
i += 2;
292+
continue;
293+
}
294+
// Handle other escape sequences like \n, \t, etc.
295+
value += char + nextChar;
296+
i += 2;
297+
continue;
298+
}
299+
// Backslash at end of string - treat as literal
300+
value += char;
301+
i++;
302+
} else if (char === quoteChar) {
303+
// Found closing quote (not escaped)
304+
return {endIndex: i, value};
305+
} else {
306+
value += char;
307+
i++;
308+
}
309+
}
310+
311+
// No closing quote found
312+
return null;
313+
}
314+
254315
/**
255316
* Extracts redirect objects from JavaScript array string
256-
* Handles both single and double quotes, and whitespace variations
317+
* Handles both single and double quotes, escaped quotes, and flexible property order
257318
*/
258319
function extractRedirectsFromArray(arrayContent: string): Redirect[] {
259320
const redirects: Redirect[] = [];
260321

261-
// Match redirect objects with more flexible whitespace handling
262-
// Handles both ' and " quotes, and various whitespace patterns including multiline
263-
// The pattern needs to match objects that span multiple lines
264-
const redirectRegex =
265-
/\{[\s\S]*?source:\s*['"]([^'"]+)['"][\s\S]*?destination:\s*['"]([^'"]+)['"][\s\S]*?\}/g;
322+
// Match redirect objects - handle both source-first and destination-first orders
323+
// Look for opening brace, then find source and destination properties in any order
324+
const objectRegex = /\{[\s\S]*?\}/g;
325+
326+
let objectMatch: RegExpExecArray | null = objectRegex.exec(arrayContent);
327+
while (objectMatch !== null) {
328+
const objectContent = objectMatch[0];
329+
let source: string | null = null;
330+
let destination: string | null = null;
331+
332+
// Find source property
333+
const sourceMatch = objectContent.match(/source\s*:\s*(['"])/);
334+
if (sourceMatch && sourceMatch.index !== undefined) {
335+
const stringStart = sourceMatch.index + sourceMatch[0].length - 1; // Position of quote char
336+
const result = extractStringValue(objectContent, stringStart);
337+
if (result) {
338+
source = result.value;
339+
}
340+
}
266341

267-
let match: RegExpExecArray | null = redirectRegex.exec(arrayContent);
268-
while (match !== null) {
269-
const source = match[1];
270-
const destination = match[2];
342+
// Find destination property
343+
const destMatch = objectContent.match(/destination\s*:\s*(['"])/);
344+
if (destMatch && destMatch.index !== undefined) {
345+
const stringStart = destMatch.index + destMatch[0].length - 1; // Position of quote char
346+
const result = extractStringValue(objectContent, stringStart);
347+
if (result) {
348+
destination = result.value;
349+
}
350+
}
271351

352+
// If both properties found, add to redirects
272353
if (source && destination) {
273354
redirects.push({
274355
source,
275356
destination,
276357
});
277358
}
278359

279-
match = redirectRegex.exec(arrayContent);
360+
objectMatch = objectRegex.exec(arrayContent);
280361
}
281362

282363
return redirects;

0 commit comments

Comments
 (0)