-
Notifications
You must be signed in to change notification settings - Fork 5
fix: fix dedenting feature #90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,6 @@ const { | |
| writeEnd, | ||
| } = require("./common"); | ||
| const { writeFile, unlink } = require("fs"); | ||
| const dedent = require("dedent"); | ||
| const path = require("path"); | ||
| const arrayBuffToBuff = require("arraybuffer-to-buffer"); | ||
| const anzip = require("anzip"); | ||
|
|
@@ -25,6 +24,9 @@ const progress = require("cli-progress"); | |
| const glob = require("glob"); | ||
| const { type } = require("os"); | ||
|
|
||
| // Deindenting dependencies | ||
| const { deindentByCommonPrefix, SENSITIVE_INDENT_EXTS } = require("./deindent"); | ||
|
|
||
| // Convert dependency functions to return promises | ||
| const writeAsync = promisify(writeFile); | ||
| const unlinkAsync = promisify(unlink); | ||
|
|
@@ -56,21 +58,37 @@ class Snippet { | |
| lines.push(textline); | ||
| } | ||
| if (config.select !== undefined) { | ||
| const selectedLines = selectLines(config.select, this.lines, this.ext); | ||
| lines.push(...selectedLines); | ||
| let snippetLines = selectLines(config.select, this.lines, this.ext); | ||
|
|
||
| if (config.enable_code_dedenting && !SENSITIVE_INDENT_EXTS.has(this.ext) && snippetLines.length) { | ||
| snippetLines = deindentByCommonPrefix(snippetLines); | ||
| } | ||
|
|
||
| lines.push(...snippetLines); | ||
| } else if(!config.startPattern && !config.endPattern ) { | ||
| lines.push(...this.lines); | ||
| let snippetLines = [...this.lines]; | ||
|
|
||
| if (config.enable_code_dedenting && !SENSITIVE_INDENT_EXTS.has(this.ext) && snippetLines.length) { | ||
| snippetLines = deindentByCommonPrefix(snippetLines); | ||
| } | ||
|
|
||
| lines.push(...snippetLines); | ||
| } else { | ||
| // use the patterns to grab the content specified. | ||
|
|
||
| const pattern = new RegExp(`(${config.startPattern}[\\s\\S]+${config.endPattern})`); | ||
| const match = this.lines.join("\n").match(pattern); | ||
|
|
||
| if (match !== null) { | ||
| let filteredLines = match[1].split("\n"); | ||
| lines.push(...filteredLines); | ||
| let snippetLines = match[1].split("\n"); | ||
|
|
||
| if (config.enable_code_dedenting && !SENSITIVE_INDENT_EXTS.has(this.ext) && snippetLines.length) { | ||
| snippetLines = deindentByCommonPrefix(snippetLines); | ||
| } | ||
|
|
||
| lines.push(...snippetLines); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (config.enable_code_block) { | ||
| lines.push(markdownCodeTicks); | ||
|
|
@@ -136,13 +154,8 @@ class File { | |
| this.lines = []; | ||
| } | ||
| // fileString converts the array of lines into a string | ||
| fileString(dedentCode = false) { | ||
| fileString() { | ||
| let lines = `${this.lines.join("\n")}\n`; | ||
|
|
||
| if (dedentCode) { | ||
| lines = dedent(lines); | ||
| } | ||
|
|
||
| return lines; | ||
| } | ||
| } | ||
|
|
@@ -467,7 +480,7 @@ class Sync { | |
| for (const file of files) { | ||
| await writeAsync( | ||
| file.fullpath, | ||
| file.fileString(this.config.features.enable_code_dedenting) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the old dedenting implementation to avoid confusing. |
||
| file.fileString() | ||
| ); | ||
| this.progress.increment(); | ||
| } | ||
|
|
@@ -530,15 +543,24 @@ function extractWriteIDAndConfig(line) { | |
| function overwriteConfig(current, extracted) { | ||
| let config = {}; | ||
|
|
||
| config.enable_source_link = | ||
| extracted?.enable_source_link ?? true | ||
| ? current.enable_source_link | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fancy looking but I thought it was too hard to read |
||
| : extracted.enable_source_link; | ||
| // use snippet override if present, otherwise use global default | ||
| if (extracted && 'enable_source_link' in extracted) { | ||
| config.enable_source_link = extracted.enable_source_link; | ||
| } else { | ||
| config.enable_source_link = current.enable_source_link; | ||
| } | ||
|
|
||
| if (extracted && 'enable_code_block' in extracted) { | ||
| config.enable_code_block = extracted.enable_code_block; | ||
| } else { | ||
| config.enable_code_block = current.enable_code_block; | ||
| } | ||
|
|
||
| config.enable_code_block = | ||
| extracted?.enable_code_block ?? true | ||
| ? current.enable_code_block | ||
| : extracted.enable_code_block; | ||
| if (extracted && 'enable_code_dedenting' in extracted) { | ||
| config.enable_code_dedenting = extracted.enable_code_dedenting; | ||
| } else { | ||
| config.enable_code_dedenting = current.enable_code_dedenting || false; | ||
| } | ||
|
|
||
| if (extracted?.highlightedLines ?? undefined) { | ||
| config.highlights = extracted.highlightedLines; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| function commonIndentPrefix(lines) { | ||
| const nonEmpty = lines.filter(l => l.trim().length > 0); | ||
| if (nonEmpty.length === 0) {return "";} | ||
|
|
||
| // Treat lines that are only closing tokens (possibly multiple) as "closers". | ||
| // Examples matched: "}", ")", "]", "});", "],", "})", "));", etc., with optional spaces. | ||
| const CLOSING_ONLY = /^\s*[\])}]+(?:[;,])?\s*$/; | ||
| const RUBY_END = /^\s*end\b\s*$/; | ||
| const isClosingOnly = (s) => CLOSING_ONLY.test(s) || RUBY_END.test(s); | ||
|
|
||
| // Ignore closers when computing the common indent | ||
| const pool = nonEmpty.filter(l => !isClosingOnly(l.trim())); | ||
| const candidates = pool.length ? pool : nonEmpty; | ||
|
|
||
| const prefixes = candidates.map(l => (l.match(/^[\t ]*/)?.[0] || "")); | ||
| let prefix = prefixes[0] || ""; | ||
| for (let i = 1; i < prefixes.length; i++) { | ||
| let j = 0; | ||
| while (j < prefix.length && j < prefixes[i].length && prefix[j] === prefixes[i][j]) {j++;} | ||
| prefix = prefix.slice(0, j); | ||
| if (!prefix) {break;} | ||
| } | ||
| return prefix; | ||
| } | ||
|
|
||
| function deindentByCommonPrefix(lines) { | ||
| const prefix = commonIndentPrefix(lines); | ||
| if (!prefix) { | ||
| return lines.slice(); | ||
| } | ||
| const re = new RegExp("^" + prefix.replace(/[\t ]/g, m => (m === "\t" ? "\\t" : " "))); | ||
| return lines.map(l => (l.startsWith(prefix) ? l.replace(re, "") : l)); | ||
| } | ||
|
|
||
| const SENSITIVE_INDENT_EXTS = new Set(['make', 'mk', 'Makefile', 'diff']); | ||
|
|
||
| module.exports = { commonIndentPrefix, deindentByCommonPrefix, SENSITIVE_INDENT_EXTS }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| const { deindentByCommonPrefix, commonIndentPrefix } = require('../src/deindent'); | ||
|
|
||
| describe('deindentByCommonPrefix', () => { | ||
| test('strips common leading spaces from all lines', () => { | ||
| const input = [ | ||
| ' foo();', | ||
| ' bar();', | ||
| ]; | ||
| const output = deindentByCommonPrefix(input); | ||
| expect(output).toEqual(['foo();', 'bar();']); | ||
| }); | ||
|
|
||
| test('preserves relative indentation inside the snippet', () => { | ||
| const input = [ | ||
| ' if (x) {', | ||
| ' doSomething();', | ||
| ' }', | ||
| ]; | ||
| const output = deindentByCommonPrefix(input); | ||
| expect(output).toEqual([ | ||
| 'if (x) {', | ||
| ' doSomething();', | ||
| '}', | ||
| ]); | ||
| }); | ||
|
|
||
| test('does not dedent when common indent is zero (hanging indent case)', () => { | ||
| const input = [ | ||
| ' a++;', | ||
| 'b = a;', | ||
| ]; | ||
| const output = deindentByCommonPrefix(input); | ||
| // Nothing stripped | ||
| expect(output).toEqual(input); | ||
| }); | ||
|
|
||
| test('ignores closing-only head lines when computing indent', () => { | ||
| const input = [ | ||
| '});', | ||
| ' doSomething();', | ||
| ]; | ||
| const output = deindentByCommonPrefix(input); | ||
| expect(output).toEqual([ | ||
| '});', // unchanged | ||
| 'doSomething();', // dedented | ||
| ]); | ||
| }); | ||
|
|
||
| test('handles empty lines gracefully', () => { | ||
| const input = [ | ||
| '', | ||
| ' foo();', | ||
| '', | ||
| ' bar();', | ||
| ]; | ||
| const output = deindentByCommonPrefix(input); | ||
| expect(output).toEqual([ | ||
| '', | ||
| 'foo();', | ||
| '', | ||
| 'bar();', | ||
| ]); | ||
| }); | ||
|
|
||
| test('returns a shallow copy when there is nothing to dedent', () => { | ||
| const input = ['foo();', 'bar();']; | ||
| const output = deindentByCommonPrefix(input); | ||
| expect(output).toEqual(input); | ||
| expect(output).not.toBe(input); // new array, not the same reference | ||
| }); | ||
| }); | ||
|
|
||
| describe('commonIndentPrefix', () => { | ||
| test('computes the correct common indent', () => { | ||
| const input = [ | ||
| ' foo();', | ||
| ' bar();', | ||
| ]; | ||
| expect(commonIndentPrefix(input)).toBe(' '); | ||
| }); | ||
|
|
||
| test('returns empty string when lines have different starting indents and at least one substantive line is flush left', () => { | ||
| const input = [ | ||
| ' foo();', | ||
| 'bar();', | ||
| ]; | ||
| expect(commonIndentPrefix(input)).toBe(''); | ||
| }); | ||
|
|
||
| test('returns empty string for all-empty input', () => { | ||
| expect(commonIndentPrefix([])).toBe(''); | ||
| expect(commonIndentPrefix(['', ''])).toBe(''); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,10 @@ | ||
| What if there is preceding text at zero indentation? Does it handle this well? | ||
|
|
||
| <!--SNIPSTART typescript-ejson-worker --> | ||
| <!--SNIPEND--> | ||
|
|
||
| For example, this paragraph starts at flush-left. | ||
|
|
||
| - However, certain items should not be dedented | ||
| - For example, this list item on another level | ||
| - Another list item |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is where we assemble the lines of the snippet and where I run the lines through the dedenting functions