@saykit/transform-js 0.9.0 → 0.10.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/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { generateSayCallExpression } from "./generator.mjs";
2
- import { isEquivalentPlaceholder, parseExpression } from "./parser.mjs";
2
+ import { collectLeadingComments, isEquivalentPlaceholder, parseExpression } from "./parser.mjs";
3
3
  import { generate } from "@babel/generator";
4
4
  import * as parser from "@babel/parser";
5
5
  import traverse_ from "@babel/traverse";
@@ -41,7 +41,7 @@ function createJsTransformer() {
41
41
  const program = createProgram(code, id);
42
42
  const messages = [];
43
43
  traverse(program, { Expression(path) {
44
- path.node.leadingComments = path.node.leadingComments ?? [];
44
+ path.node.leadingComments = collectLeadingComments(path);
45
45
  const message = parseExpression(path.node);
46
46
  if (message) {
47
47
  assignSequenceIdentifiers(message, { current: 0 }, isEquivalentPlaceholder);
@@ -61,7 +61,6 @@ function createJsTransformer() {
61
61
  transform(code, id) {
62
62
  const program = createProgram(code, id);
63
63
  traverse(program, { Expression(path) {
64
- path.node.leadingComments = path.node.leadingComments ?? [];
65
64
  const message = parseExpression(path.node);
66
65
  if (message) {
67
66
  assignSequenceIdentifiers(message, { current: 0 }, isEquivalentPlaceholder);
package/dist/parser.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import { NodePath } from "@babel/traverse";
1
2
  import { AUTO_INCREMENT_IDENTIFIER, ArgumentMessage, CompositeMessage } from "@saykit/config/features/messages";
2
3
  import * as t from "@babel/types";
3
4
  //#region src/parser.d.ts
@@ -9,10 +10,9 @@ declare function processExpression(expression: t.Node): [t.Expression, t.ObjectE
9
10
  * value alone, so the wrapper never reaches the output: `${{ total: sum() }}`
10
11
  * is `{total}` rather than `{0}`.
11
12
  *
12
- * An inline one-key object is the only shape this claims, and it is a shape
13
- * that could not mean anything else interpolating an object stringifies it
14
- * to `[object Object]`, and rendering one in JSX throws. Anything else is
15
- * returned untouched and named the way it always was.
13
+ * An inline one-key object is the only shape this claims, and it could not mean
14
+ * anything else: interpolating an object stringifies it to `[object Object]`.
15
+ * Anything else is returned untouched.
16
16
  */
17
17
  declare function unwrapPlaceholder(expression: t.Expression): [string | typeof AUTO_INCREMENT_IDENTIFIER, t.Expression];
18
18
  /**
@@ -21,12 +21,25 @@ declare function unwrapPlaceholder(expression: t.Expression): [string | typeof A
21
21
  *
22
22
  * A value is compared whole: the same variable interpolated twice is one value,
23
23
  * while `${name}` beside `${{ name: author.name }}` is two things claiming one
24
- * name. An element is compared on its opening element alone `say-tag` has
25
- * been stripped by the time this runs, and its children come from the
26
- * translation rather than the source.
24
+ * name. An element is compared on its opening element alone, since its children
25
+ * come from the translation rather than the source.
27
26
  */
28
27
  declare function isEquivalentPlaceholder(a: any, b: any): boolean;
29
28
  declare function parseExpression(expression: t.Expression, fallback?: false): CompositeMessage | null;
30
29
  declare function parseExpression(expression: t.Expression, fallback: true): CompositeMessage | ArgumentMessage;
30
+ /**
31
+ * The notes a translator is meant to read, taken from the comments above a
32
+ * message: `// TRANSLATORS: keep this under 20 characters`. A comment without
33
+ * the marker is a note to whoever is reading the code, and stays there.
34
+ */
35
+ declare function getTranslatorComments(comments: readonly t.Comment[] | null | undefined): string[];
36
+ /**
37
+ * The comments written in front of a message, in the order they were written.
38
+ *
39
+ * Babel attaches a comment on its own line to whatever node begins at it, so a
40
+ * message is rarely the node holding its own notes. Walking out to the
41
+ * statement gathers them from wherever they landed.
42
+ */
43
+ declare function collectLeadingComments(path: NodePath<t.Node>): t.Comment[];
31
44
  //#endregion
32
- export { isEquivalentPlaceholder, parseCallExpression, parseExpression, parseTaggedTemplateExpression, processExpression, unwrapPlaceholder };
45
+ export { collectLeadingComments, getTranslatorComments, isEquivalentPlaceholder, parseCallExpression, parseExpression, parseTaggedTemplateExpression, processExpression, unwrapPlaceholder };
package/dist/parser.mjs CHANGED
@@ -10,7 +10,7 @@ function parseTaggedTemplateExpression(tagged) {
10
10
  return new CompositeMessage({
11
11
  id: descriptor ? findPropertyValueIfStringLiteralAsString(descriptor, "id") : void 0,
12
12
  context: descriptor ? findPropertyValueIfStringLiteralAsString(descriptor, "context") : void 0
13
- }, getTranslatorComments(tagged.leadingComments ?? []), tagged.loc ? [`${tagged.loc.filename}:${tagged.loc.start.line}`] : [], children, accessor);
13
+ }, getTranslatorComments(tagged.leadingComments), tagged.loc ? [`${tagged.loc.filename}:${tagged.loc.start.line}`] : [], children, accessor);
14
14
  }
15
15
  function buildTemplateChildren(template) {
16
16
  return template.quasis.reduce((c, q, i) => {
@@ -28,7 +28,7 @@ function parseCallExpression(call) {
28
28
  const wrap = (children) => new CompositeMessage({
29
29
  id: descriptorId,
30
30
  context: descriptorContext
31
- }, [], call.loc ? [`${call.loc.filename}:${call.loc.start.line}`] : [], children, accessor);
31
+ }, getTranslatorComments(call.leadingComments), call.loc ? [`${call.loc.filename}:${call.loc.start.line}`] : [], children, accessor);
32
32
  if (typeof kind === "string" && [
33
33
  "select",
34
34
  "ordinal",
@@ -174,12 +174,43 @@ function findPropertyValueIfStringLiteralAsString(object, key) {
174
174
  if (t.isIdentifier(property.key) && property.key.name === key && t.isStringLiteral(property.value)) return property.value.value;
175
175
  }
176
176
  }
177
+ const TRANSLATOR_COMMENT_PREFIX = "translators:";
177
178
  function getTranslatorComments(comments) {
178
- return comments.reduce((a, c) => {
179
+ return (comments ?? []).reduce((a, c) => {
179
180
  const text = c.value.trim();
180
- if (text.toLowerCase().startsWith("translators:")) a.push(text.slice(12).trim());
181
+ if (text.toLowerCase().startsWith(TRANSLATOR_COMMENT_PREFIX)) a.push(text.slice(12).trim());
181
182
  return a;
182
183
  }, []);
183
184
  }
185
+ function collectLeadingComments(path) {
186
+ const levels = [];
187
+ let current = path;
188
+ while (current) {
189
+ levels.push(current.node.leadingComments ?? []);
190
+ const parent = current.parentPath;
191
+ /* v8 ignore next */
192
+ if (!parent) break;
193
+ if (t.isJSXElement(parent.node) || t.isJSXFragment(parent.node)) {
194
+ levels.push(getPrecedingJSXComments(parent.node, current.node));
195
+ break;
196
+ }
197
+ if (t.isStatement(current.node) && !t.isExportDeclaration(parent.node)) break;
198
+ current = parent;
199
+ }
200
+ return [...new Set(levels.reverse().flat())];
201
+ }
202
+ function getPrecedingJSXComments(parent, node) {
203
+ const index = parent.children.indexOf(node);
204
+ /* v8 ignore next */
205
+ if (index === -1) return [];
206
+ const comments = [];
207
+ for (let i = index - 1; i >= 0; i--) {
208
+ const sibling = parent.children[i];
209
+ if (t.isJSXText(sibling) && !sibling.value.trim()) continue;
210
+ if (!t.isJSXExpressionContainer(sibling) || !t.isJSXEmptyExpression(sibling.expression)) break;
211
+ comments.unshift(...sibling.expression.innerComments ?? []);
212
+ }
213
+ return comments;
214
+ }
184
215
  //#endregion
185
- export { isEquivalentPlaceholder, parseCallExpression, parseExpression, parseTaggedTemplateExpression, processExpression, unwrapPlaceholder };
216
+ export { collectLeadingComments, getTranslatorComments, isEquivalentPlaceholder, parseCallExpression, parseExpression, parseTaggedTemplateExpression, processExpression, unwrapPlaceholder };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saykit/transform-js",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "JavaScript and TypeScript transformer for SayKit",
5
5
  "keywords": [
6
6
  "i18n",
@@ -47,7 +47,7 @@
47
47
  "@babel/parser": "^7.29.7",
48
48
  "@babel/traverse": "^7.29.7",
49
49
  "@babel/types": "^7.29.7",
50
- "@saykit/config": "^0.9.0"
50
+ "@saykit/config": "^0.10.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@babel/core": "^7.29.7",