First commit.

Signed-off-by: Chen Xiao <abigwc@gmail.com>
This commit is contained in:
Chen Xiao
2026-05-08 14:43:16 +08:00
commit 0b64e2de94
10989 changed files with 2253791 additions and 0 deletions
+554
View File
@@ -0,0 +1,554 @@
import { dedent } from '..';
function tag(strings: TemplateStringsArray, ...values: number[]) {
let string = strings[0];
values.forEach((value, i) => {
string += 2 * value + strings[i + 1];
});
return string;
}
describe('dedent tag', () => {
it('should work with empty string', () => {
expect(dedent``).toEqual('');
});
it('should work with tabs', () => {
expect(dedent`Line #1
Line #2
Line #3`).toEqual('Line #1\nLine #2\nLine #3');
expect(dedent`Line #${1}
Line #${2}
Line #${3}`).toEqual('Line #1\nLine #2\nLine #3');
expect(dedent`${1}. line #${1}
${2}. line #${2}
${3}. line`).toEqual('1. line #1\n2. line #2\n3. line');
});
it('should work with spaces', () => {
expect(dedent`Line #1
Line #2
Line #3`).toEqual('Line #1\nLine #2\nLine #3');
expect(dedent`Line #${1}
Line #${2}
Line #${3}`).toEqual('Line #1\nLine #2\nLine #3');
expect(dedent`${1}. line #${1}
${2}. line #${2}
${3}. line`).toEqual('1. line #1\n2. line #2\n3. line');
});
it('should remove leading/trailing line break', () => {
expect(
dedent`
Line #1
Line #2
Line #3
`,
).toEqual('Line #1\nLine #2\nLine #3');
expect(
dedent`
Line #1
Line #2
Line #3
`,
).toEqual('Line #1\n\tLine #2\n\tLine #3');
expect(
dedent`
Line #${1}
Line #${2}
Line #${3}
`,
).toEqual('Line #1\nLine #2\nLine #3');
expect(
dedent`
Line #${1}
Line #${2}
Line #${3}
`,
).toEqual('Line #1\n\tLine #2\n\tLine #3');
expect(
dedent`
${1}. line #${1}
${2}. line #${2}
${3}. line
`,
).toEqual('1. line #1\n2. line #2\n3. line');
});
it('should not remove more than one leading/trailing line break', () => {
expect(
dedent`
Line #1
Line #2
Line #3
`,
).toEqual('\nLine #1\nLine #2\nLine #3\n');
expect(
dedent`
Line #${1}
Line #${2}
Line #${3}
`,
).toEqual('\nLine #1\nLine #2\nLine #3\n');
expect(
dedent`
${1}. line #${1}
${2}. line #${2}
${3}. line
`,
).toEqual('\n1. line #1\n2. line #2\n3. line\n');
});
it('should remove the same number of tabs/spaces from each line', () => {
expect(
dedent`
Line #1
Line #2
Line #3
`,
).toEqual('Line #1\n\tLine #2\n\t\tLine #3');
expect(
dedent`
Line #${1}
Line #${2}
Line #${3}
`,
).toEqual('Line #1\n\tLine #2\n\t\tLine #3');
expect(
dedent`
${1}. line #${1}
${2}. line #${2}
${3}. line
`,
).toEqual('1. line #1\n\t2. line #2\n\t\t3. line');
});
it("should ignore the last line if it doesn't contain anything else than whitespace", () => {
expect(
(() => {
return dedent`
Line #1
Line #2
Line #3
`;
})(),
).toEqual('Line #1\nLine #2\nLine #3');
expect(
(() => {
return dedent`
Line #${1}
Line #${2}
Line #${3}
`;
})(),
).toEqual('Line #1\nLine #2\nLine #3');
expect(
(() => {
return dedent`
${1}. line #${1}
${2}. line #${2}
${3}. line
`;
})(),
).toEqual('1. line #1\n2. line #2\n3. line');
});
it("should process escape sequences", () => {
expect(
(() => {
return dedent`
\${not interpolated}
\`
`;
})(),
).toEqual('${not interpolated}\n`');
});
});
describe('dedent() function', () => {
it('should work with tabs', () => {
expect(
dedent(`Line #1
Line #2
Line #3`),
).toEqual('Line #1\nLine #2\nLine #3');
expect(
dedent(`Line #${1}
Line #${2}
Line #${3}`),
).toEqual('Line #1\nLine #2\nLine #3');
expect(
dedent(`${1}. line #${1}
${2}. line #${2}
${3}. line`),
).toEqual('1. line #1\n2. line #2\n3. line');
});
it('should work with spaces', () => {
expect(
dedent(`Line #1
Line #2
Line #3`),
).toEqual('Line #1\nLine #2\nLine #3');
expect(
dedent(`Line #${1}
Line #${2}
Line #${3}`),
).toEqual('Line #1\nLine #2\nLine #3');
expect(
dedent(`${1}. line #${1}
${2}. line #${2}
${3}. line`),
).toEqual('1. line #1\n2. line #2\n3. line');
});
it('should remove leading/trailing line break', () => {
expect(
dedent(`
Line #1
Line #2
Line #3
`),
).toEqual('Line #1\nLine #2\nLine #3');
expect(
dedent(`
Line #1
Line #2
Line #3
`),
).toEqual('Line #1\n\tLine #2\n\tLine #3');
expect(
dedent(`
Line #${1}
Line #${2}
Line #${3}
`),
).toEqual('Line #1\nLine #2\nLine #3');
expect(
dedent(`
Line #${1}
Line #${2}
Line #${3}
`),
).toEqual('Line #1\n\tLine #2\n\tLine #3');
expect(
dedent(`
${1}. line #${1}
${2}. line #${2}
${3}. line
`),
).toEqual('1. line #1\n2. line #2\n3. line');
});
it('should not remove more than one leading/trailing line break', () => {
expect(
dedent(`
Line #1
Line #2
Line #3
`),
).toEqual('\nLine #1\nLine #2\nLine #3\n');
expect(
dedent(`
Line #${1}
Line #${2}
Line #${3}
`),
).toEqual('\nLine #1\nLine #2\nLine #3\n');
expect(
dedent(`
${1}. line #${1}
${2}. line #${2}
${3}. line
`),
).toEqual('\n1. line #1\n2. line #2\n3. line\n');
});
it('should remove the same number of tabs/spaces from each line', () => {
expect(
dedent(`
Line #1
Line #2
Line #3
`),
).toEqual('Line #1\n\tLine #2\n\t\tLine #3');
expect(
dedent(`
Line #${1}
Line #${2}
Line #${3}
`),
).toEqual('Line #1\n\tLine #2\n\t\tLine #3');
expect(
dedent(`
${1}. line #${1}
${2}. line #${2}
${3}. line
`),
).toEqual('1. line #1\n\t2. line #2\n\t\t3. line');
});
it("should ignore the last line if it doesn't contain anything else than whitespace", () => {
expect(
(() => {
return dedent(`
Line #1
Line #2
Line #3
`);
})(),
).toEqual('Line #1\nLine #2\nLine #3');
expect(
(() => {
return dedent(`
Line #${1}
Line #${2}
Line #${3}
`);
})(),
).toEqual('Line #1\nLine #2\nLine #3');
expect(
(() => {
return dedent(`
${1}. line #${1}
${2}. line #${2}
${3}. line
`);
})(),
).toEqual('1. line #1\n2. line #2\n3. line');
});
it("should process escape sequences", () => {
expect(
dedent(`
\${not interpolated}
\`
`),
).toEqual('${not interpolated}\n`');
});
});
describe('dedent() function with custom tag', () => {
it('should work with tabs', () => {
expect(
dedent(tag`Line #1
Line #2
Line #3`),
).toEqual('Line #1\nLine #2\nLine #3');
expect(
dedent(tag`Line #${1}
Line #${2}
Line #${3}`),
).toEqual('Line #2\nLine #4\nLine #6');
expect(
dedent(tag`${1}. line #${1}
${2}. line #${2}
${3}. line`),
).toEqual('2. line #2\n4. line #4\n6. line');
});
it('should work with spaces', () => {
expect(
dedent(tag`Line #1
Line #2
Line #3`),
).toEqual('Line #1\nLine #2\nLine #3');
expect(
dedent(tag`Line #${1}
Line #${2}
Line #${3}`),
).toEqual('Line #2\nLine #4\nLine #6');
expect(
dedent(tag`${1}. line #${1}
${2}. line #${2}
${3}. line`),
).toEqual('2. line #2\n4. line #4\n6. line');
});
it('should remove leading/trailing line break', () => {
expect(
dedent(tag`
Line #1
Line #2
Line #3
`),
).toEqual('Line #1\nLine #2\nLine #3');
expect(
dedent(tag`
Line #1
Line #2
Line #3
`),
).toEqual('Line #1\n\tLine #2\n\tLine #3');
expect(
dedent(tag`
Line #${1}
Line #${2}
Line #${3}
`),
).toEqual('Line #2\nLine #4\nLine #6');
expect(
dedent(tag`
Line #${1}
Line #${2}
Line #${3}
`),
).toEqual('Line #2\n\tLine #4\n\tLine #6');
expect(
dedent(tag`
${1}. line #${1}
${2}. line #${2}
${3}. line
`),
).toEqual('2. line #2\n4. line #4\n6. line');
});
it('should not remove more than one leading/trailing line break', () => {
expect(
dedent(tag`
Line #1
Line #2
Line #3
`),
).toEqual('\nLine #1\nLine #2\nLine #3\n');
expect(
dedent(tag`
Line #${1}
Line #${2}
Line #${3}
`),
).toEqual('\nLine #2\nLine #4\nLine #6\n');
expect(
dedent(tag`
${1}. line #${1}
${2}. line #${2}
${3}. line
`),
).toEqual('\n2. line #2\n4. line #4\n6. line\n');
});
it('should remove the same number of tabs/spaces from each line', () => {
expect(
dedent(tag`
Line #1
Line #2
Line #3
`),
).toEqual('Line #1\n\tLine #2\n\t\tLine #3');
expect(
dedent(tag`
Line #${1}
Line #${2}
Line #${3}
`),
).toEqual('Line #2\n\tLine #4\n\t\tLine #6');
expect(
dedent(tag`
${1}. line #${1}
${2}. line #${2}
${3}. line
`),
).toEqual('2. line #2\n\t4. line #4\n\t\t6. line');
});
it("should ignore the last line if it doesn't contain anything else than whitespace", () => {
expect(
(() => {
return dedent(tag`
Line #1
Line #2
Line #3
`);
})(),
).toEqual('Line #1\nLine #2\nLine #3');
expect(
(() => {
return dedent(tag`
Line #${1}
Line #${2}
Line #${3}
`);
})(),
).toEqual('Line #2\nLine #4\nLine #6');
expect(
(() => {
return dedent(tag`
${1}. line #${1}
${2}. line #${2}
${3}. line
`);
})(),
).toEqual('2. line #2\n4. line #4\n6. line');
});
it("should process escape sequences", () => {
expect(
dedent(tag`
\${not interpolated}
\`
`),
).toEqual('${not interpolated}\n`');
});
});
+88
View File
@@ -0,0 +1,88 @@
import { dedent } from '..';
describe('Issue 21', () => {
it('should dedent nested dedents correctly', () => {
const fieldDocs = dedent`
* a
* b
* c
`
const a = dedent`
/**
${fieldIntro()}
*
${fieldDocs}
*
${fieldExample()}
*/
`
function fieldIntro() {
return dedent`
* 0
`
}
function fieldExample() {
return dedent`
* d
`
}
const expected = `/**
* 0
*
* a
* b
* c
*
* d
*/`
expect(a).toEqual(expected);
});
/**
* Could not find a way to handle this but is an edge case we'd like to solve eventually
*/
it.skip('should handle function calls of nested dedents correctly', () => {
const fieldDocs = dedent(`
* a
* b
* c
`)
const a = dedent(`
/**
${fieldIntro()}
*
${fieldDocs}
*
${fieldExample()}
*/
`)
function fieldIntro() {
return dedent(`
* 0
`)
}
function fieldExample() {
return dedent(`
* d
`)
}
const expected = `/**
* 0
*
* a
* b
* c
*
* d
*/`
expect(a).toEqual(expected);
});
});
+58
View File
@@ -0,0 +1,58 @@
export function dedent(
templ: TemplateStringsArray | string,
...values: unknown[]
): string {
let strings = Array.from(typeof templ === 'string' ? [templ] : templ);
// 1. Remove trailing whitespace.
strings[strings.length - 1] = strings[strings.length - 1].replace(
/\r?\n([\t ]*)$/,
'',
);
// 2. Find all line breaks to determine the highest common indentation level.
const indentLengths = strings.reduce((arr, str) => {
const matches = str.match(/\n([\t ]+|(?!\s).)/g);
if (matches) {
return arr.concat(
matches.map((match) => match.match(/[\t ]/g)?.length ?? 0),
);
}
return arr;
}, <number[]>[]);
// 3. Remove the common indentation from all strings.
if (indentLengths.length) {
const pattern = new RegExp(`\n[\t ]{${Math.min(...indentLengths)}}`, 'g');
strings = strings.map((str) => str.replace(pattern, '\n'));
}
// 4. Remove leading whitespace.
strings[0] = strings[0].replace(/^\r?\n/, '');
// 5. Perform interpolation.
let string = strings[0];
values.forEach((value, i) => {
// 5.1 Read current indentation level
const endentations = string.match(/(?:^|\n)( *)$/)
const endentation = endentations ? endentations[1] : ''
let indentedValue = value
// 5.2 Add indentation to values with multiline strings
if (typeof value === 'string' && value.includes('\n')) {
indentedValue = String(value)
.split('\n')
.map((str, i) => {
return i === 0 ? str : `${endentation}${str}`
})
.join('\n');
}
string += indentedValue + strings[i + 1];
});
return string;
}
export default dedent;