Skip to content

Commit c6fdfad

Browse files
authored
Simplify snippet script (firebase#78)
1 parent 156cded commit c6fdfad

File tree

1 file changed

+87
-32
lines changed

1 file changed

+87
-32
lines changed

scripts/separate-snippets.ts

Lines changed: 87 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,27 @@ function isBlank(line: string) {
3131
}
3232

3333
/**
34-
* Turns a series of source lines into a standalone snippet file by:
35-
* - Converting require statements into top-level imports.
36-
* - Adjusting indentation to left-align all content
37-
* - Removing any blank lines at the starts
38-
* - Adding a suffix to snippet names
39-
*
40-
* @param lines the lines containing the snippet (including START/END comments)
41-
* @param sourceFile the source file where the original snippet lives
42-
* @param snippetSuffix the suffix (such as _modular)
34+
* Replace all const { foo } = require('bar') with import { foo } from 'bar';
4335
*/
44-
function processSnippet(
45-
lines: string[],
46-
sourceFile: string,
47-
snippetSuffix: string
48-
): string {
49-
const outputLines: string[] = [];
50-
36+
function replaceRequireWithImport(lines: string[]) {
37+
const outputLines = [];
5138
for (const line of lines) {
5239
if (line.match(RE_REQUIRE)) {
5340
outputLines.push(line.replace(RE_REQUIRE, `import {$1} from $2`));
54-
} else if (line.match(RE_START_SNIPPET)) {
41+
} else {
42+
outputLines.push(line);
43+
}
44+
}
45+
return outputLines;
46+
}
47+
48+
/**
49+
* Change all [START foo] and [END foo] to be [START foosuffix] and [END foosuffix]
50+
*/
51+
function addSuffixToSnippetNames(lines: string[], snippetSuffix: string) {
52+
const outputLines = [];
53+
for (const line of lines) {
54+
if (line.match(RE_START_SNIPPET)) {
5555
outputLines.push(line.replace(RE_START_SNIPPET, `[START $1${snippetSuffix}]`));
5656
} else if (line.match(RE_END_SNIPPET)) {
5757
outputLines.push(
@@ -61,37 +61,74 @@ function processSnippet(
6161
outputLines.push(line);
6262
}
6363
}
64+
return outputLines;
65+
}
6466

65-
// Adjust indentation of the otherLines so that they're left aligned
66-
const nonBlankLines = outputLines.filter((l) => !isBlank(l));
67+
/**
68+
* Remove all left-padding so that the least indented line is left-aligned.
69+
*/
70+
function adjustIndentation(lines: string[]) {
71+
const nonBlankLines = lines.filter((l) => !isBlank(l));
6772
const indentSizes = nonBlankLines.map((l) => l.length - l.trimLeft().length);
6873
const minIndent = Math.min(...indentSizes);
6974

70-
const adjustedOutputLines: string[] = [];
71-
for (const line of outputLines) {
75+
const outputLines = [];
76+
for (const line of lines) {
7277
if (isBlank(line)) {
73-
adjustedOutputLines.push("");
78+
outputLines.push("");
7479
} else {
75-
adjustedOutputLines.push(line.substr(minIndent));
80+
outputLines.push(line.substr(minIndent));
7681
}
7782
}
83+
return outputLines;
84+
}
7885

79-
// Special case: if the first line after the comments is blank we want to remove it
80-
const firstNonComment = adjustedOutputLines.findIndex(
86+
/**
87+
* If the first line after leading comments is blank, remove it.
88+
*/
89+
function removeFirstLineAfterComments(lines: string[]) {
90+
const outputLines = [...lines];
91+
92+
const firstNonComment = outputLines.findIndex(
8193
(l) => !l.startsWith("//")
8294
);
8395
if (isBlank(outputLines[firstNonComment])) {
84-
adjustedOutputLines.splice(firstNonComment, 1);
96+
outputLines.splice(firstNonComment, 1);
8597
}
8698

99+
return outputLines;
100+
}
101+
102+
/**
103+
* Turns a series of source lines into a standalone snippet file by running
104+
* a series of transformations.
105+
*
106+
* @param lines the lines containing the snippet (including START/END comments)
107+
* @param sourceFile the source file where the original snippet lives (used in preamble)
108+
* @param snippetSuffix the suffix (such as _modular)
109+
*/
110+
function processSnippet(
111+
lines: string[],
112+
sourceFile: string,
113+
snippetSuffix: string
114+
): string {
115+
let outputLines = [...lines];
116+
117+
// Perform transformations individually, in order
118+
outputLines = replaceRequireWithImport(outputLines);
119+
outputLines = addSuffixToSnippetNames(outputLines, snippetSuffix);
120+
outputLines = adjustIndentation(outputLines);
121+
outputLines = removeFirstLineAfterComments(outputLines);
122+
123+
// Add a preamble to every snippet
87124
const preambleLines = [
88125
`// This snippet file was generated by processing the source file:`,
89126
`// ${sourceFile}`,
90127
`//`,
91128
`// To make edits to the snippets in this file, please edit the source`,
92129
``,
93130
];
94-
const content = [...preambleLines, ...adjustedOutputLines].join("\n");
131+
const content = [...preambleLines, ...outputLines].join("\n");
95132
return content;
96133
}
97134

@@ -121,40 +158,55 @@ function collectSnippets(filePath: string): SnippetsConfig {
121158
map: {},
122159
};
123160

161+
// If a file does not have '// [SNIPPETS_SEPARATION enabled]' in it then
162+
// we don't process it for this script.
124163
config.enabled = lines.some((l) => !!l.match(RE_SNIPPETS_SEPARATION));
125164
if (!config.enabled) {
126165
return config;
127166
}
128167

168+
// If the file contains '// [SNIPPETS_SUFFIX _banana]' we use _banana (or whatever)
169+
// as the suffix. Otherwise we default to _modular.
129170
const suffixLine = lines.find((l) => !!l.match(RE_SNIPPETS_SUFFIX));
130171
if (suffixLine) {
131172
const m = suffixLine.match(RE_SNIPPETS_SUFFIX);
132173
config.suffix = m[1];
133174
}
134175

176+
// A temporary array holding the names of snippets we're currently within.
177+
// This allows for handling nested snippets.
135178
let inSnippetNames = [];
136179

137180
for (const line of lines) {
138181
const startMatch = line.match(RE_START_SNIPPET);
139182
const endMatch = line.match(RE_END_SNIPPET);
140183

141184
if (startMatch) {
185+
// When we find a new [START foo] tag we are now inside snippet 'foo'.
186+
// Until we find an [END foo] tag. All lines we see between now and then
187+
// are part of the snippet content.
142188
const snippetName = startMatch[1];
143-
config.map[snippetName] = [];
144-
config.map[snippetName].push(line);
145-
189+
config.map[snippetName] = [line];
146190
inSnippetNames.push(snippetName);
147191
} else if (endMatch) {
192+
// When we find a new [END foo] tag we are now exiting snippet 'foo'.
148193
const snippetName = endMatch[1];
149-
config.map[snippetName].push(line);
150194

195+
// If we were not aware that we were inside this snippet (no previous START)
196+
// then we hard throw.
151197
if (!inSnippetNames.includes(snippetName)) {
152198
throw new Error(
153199
`Unrecognized END tag ${snippetName} in ${filePath}.`
154200
);
155201
}
202+
203+
// Collect this line as the final line of the snippet and then
204+
// remove this snippet name from the list we're tracking.
205+
config.map[snippetName].push(line);
156206
inSnippetNames.splice(inSnippetNames.indexOf(snippetName), 1);
157207
} else if (inSnippetNames.length > 0) {
208+
// Any line that is not START or END is appended to the list of
209+
// lines for all the snippets we're currently tracking.
158210
for (const snippetName of inSnippetNames) {
159211
config.map[snippetName].push(line);
160212
}
@@ -189,11 +241,14 @@ async function main() {
189241

190242
for (const snippetName in config.map) {
191243
const newFilePath = path.join(snippetDir, `${snippetName}.js`);
244+
245+
const snippetLines = config.map[snippetName];
192246
const content = processSnippet(
193-
config.map[snippetName],
247+
snippetLines,
194248
filePath,
195249
config.suffix
196250
);
251+
197252
fs.writeFileSync(newFilePath, content);
198253
}
199254
}

0 commit comments

Comments
 (0)