@saykit/transform-jsx 0.8.0 → 0.9.1

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
@@ -5,7 +5,7 @@ import * as parser from "@babel/parser";
5
5
  import traverse_ from "@babel/traverse";
6
6
  import { assignSequenceIdentifiers } from "@saykit/config/features/messages";
7
7
  import { generateSayCallExpression } from "@saykit/transform-js/generator";
8
- import { isEquivalentPlaceholder, parseExpression } from "@saykit/transform-js/parser";
8
+ import { collectLeadingComments, isEquivalentPlaceholder, parseExpression } from "@saykit/transform-js/parser";
9
9
  //#region src/index.ts
10
10
  const traverse = traverse_.default || traverse_;
11
11
  function createProgram(code, id) {
@@ -37,7 +37,7 @@ function createJsxTransformer() {
37
37
  const messages = [];
38
38
  traverse(program, {
39
39
  Expression(path) {
40
- path.node.leadingComments = path.node.leadingComments ?? [];
40
+ path.node.leadingComments = collectLeadingComments(path);
41
41
  const message = parseExpression(path.node);
42
42
  if (message) {
43
43
  assignSequenceIdentifiers(message, { current: 0 }, isEquivalentPlaceholder);
@@ -46,7 +46,7 @@ function createJsxTransformer() {
46
46
  }
47
47
  },
48
48
  JSXElement(path) {
49
- path.node.leadingComments = path.node.leadingComments ?? [];
49
+ path.node.leadingComments = collectLeadingComments(path);
50
50
  const message = parseJSXElement(path.node);
51
51
  if (message) {
52
52
  assignSequenceIdentifiers(message, { current: 0 }, isEquivalentPlaceholder);
@@ -68,7 +68,6 @@ function createJsxTransformer() {
68
68
  const program = createProgram(code, id);
69
69
  traverse(program, {
70
70
  Expression(path) {
71
- path.node.leadingComments = path.node.leadingComments ?? [];
72
71
  const message = parseExpression(path.node);
73
72
  if (message) {
74
73
  assignSequenceIdentifiers(message, { current: 0 }, isEquivalentPlaceholder);
@@ -78,7 +77,6 @@ function createJsxTransformer() {
78
77
  }
79
78
  },
80
79
  JSXElement(path) {
81
- path.node.leadingComments = path.node.leadingComments ?? [];
82
80
  const message = parseJSXElement(path.node);
83
81
  if (message) {
84
82
  assignSequenceIdentifiers(message, { current: 0 }, isEquivalentPlaceholder);
package/dist/parser.d.mts CHANGED
@@ -2,7 +2,12 @@ import { CompositeMessage, ElementMessage } from "@saykit/config/features/messag
2
2
  import * as t from "@babel/types";
3
3
  //#region src/parser.d.ts
4
4
  declare function parseJSXContainerElement(element: t.JSXElement): CompositeMessage | null;
5
- declare function parseJSXOpeningElement(element: t.JSXOpeningElement): CompositeMessage | null;
5
+ /**
6
+ * A self-closing `Say` element. Comments are passed in rather than read off the
7
+ * node, since they were written in front of the element as a whole and Babel
8
+ * attaches them there.
9
+ */
10
+ declare function parseJSXOpeningElement(element: t.JSXOpeningElement, comments?: readonly t.Comment[] | null): CompositeMessage | null;
6
11
  declare function parseJSXElement(element: t.JSXElement, fallback?: false): CompositeMessage | null;
7
12
  declare function parseJSXElement(element: t.JSXElement, fallback: true): CompositeMessage | ElementMessage;
8
13
  //#endregion
package/dist/parser.mjs CHANGED
@@ -1,12 +1,7 @@
1
1
  import { AUTO_INCREMENT_IDENTIFIER, ArgumentMessage, ChoiceMessage, CompositeMessage, ElementMessage, LiteralMessage, isArgumentType, validateArgumentStyle, validateBranchIdentifier } from "@saykit/config/features/messages";
2
- import { unwrapPlaceholder } from "@saykit/transform-js/parser";
2
+ import { getTranslatorComments, unwrapPlaceholder } from "@saykit/transform-js/parser";
3
3
  import * as t from "@babel/types";
4
4
  //#region src/parser.ts
5
- /**
6
- * Attribute used to name the ICU tag an embedded element extracts as, e.g.
7
- * `<a say-tag="link">` becomes `<link></link>` rather than `<0></0>`. It never
8
- * reaches the real element.
9
- */
10
5
  const TAG_ATTRIBUTE = "say-tag";
11
6
  const TAG_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
12
7
  function parseJSXContainerElement(element) {
@@ -21,9 +16,9 @@ function parseJSXContainerElement(element) {
21
16
  return new CompositeMessage({
22
17
  id: descriptorId,
23
18
  context: descriptorContext
24
- }, [], getReferences(element), children, accessor, whitespace);
19
+ }, getTranslatorComments(element.leadingComments), getReferences(element), children, accessor, whitespace);
25
20
  }
26
- function parseJSXOpeningElement(element) {
21
+ function parseJSXOpeningElement(element, comments) {
27
22
  if (!element.selfClosing) return null;
28
23
  const processed = processJSXOpeningElement(element);
29
24
  if (!processed) return null;
@@ -33,7 +28,7 @@ function parseJSXOpeningElement(element) {
33
28
  const wrap = (children) => new CompositeMessage({
34
29
  id: descriptorId,
35
30
  context: descriptorContext
36
- }, [], getReferences(element), children, accessor);
31
+ }, getTranslatorComments(comments), getReferences(element), children, accessor);
37
32
  if (typeof kind === "string" && [
38
33
  "select",
39
34
  "ordinal",
@@ -90,11 +85,6 @@ function parseJSXOpeningElement(element) {
90
85
  }
91
86
  return null;
92
87
  }
93
- /**
94
- * The `offset` a plural subtracts from its selector before `#` is formatted.
95
- * See the JS parser's counterpart — the constraints are the same, only the
96
- * syntax carrying it differs.
97
- */
98
88
  function findPluralOffset(attributes) {
99
89
  for (const attribute of attributes) {
100
90
  if (!t.isJSXAttribute(attribute)) continue;
@@ -106,14 +96,6 @@ function findPluralOffset(attributes) {
106
96
  return expression.value;
107
97
  }
108
98
  }
109
- /**
110
- * The children the JSX transform itself would compile, so a message extracts as
111
- * the text that renders. Text children come back with their whitespace
112
- * collapsed the way JSX collapses it — a line break and the indentation around
113
- * it are layout and disappear, while two lines that both hold text rejoin with
114
- * the single space that separated the words — every expression container comes
115
- * back unwrapped, and a comment child comes back as nothing.
116
- */
117
99
  function buildMessageChildren(element, into = []) {
118
100
  return t.react.buildChildren(element).reduce((p, c) => {
119
101
  const literal = getExpressionAsLiteralText(c);
@@ -127,26 +109,12 @@ function buildMessageChildren(element, into = []) {
127
109
  return p;
128
110
  }, into);
129
111
  }
130
- /**
131
- * Add text to the message, folding it into the literal in front of it when
132
- * there is one, so `Hello{' '}world` is three children in the source and one
133
- * run of text in the catalogue.
134
- */
135
112
  function pushLiteral(messages, text) {
136
113
  if (!text) return;
137
114
  const last = messages.at(-1);
138
115
  if (last instanceof LiteralMessage) messages[messages.length - 1] = new LiteralMessage(last.text + text);
139
116
  else messages.push(new LiteralMessage(text));
140
117
  }
141
- /**
142
- * The text a child renders as, when that is knowable at build time — a text
143
- * child, which `buildChildren` has already collapsed into a string, or an
144
- * expression holding a literal with nothing interpolated into it.
145
- *
146
- * The second is what makes `{' '}` the way to write whitespace that has to
147
- * survive a line break: it extracts as the space it renders as, rather than as
148
- * a placeholder a translator can neither see nor move.
149
- */
150
118
  function getExpressionAsLiteralText(expression) {
151
119
  if (t.isStringLiteral(expression)) return expression.value;
152
120
  if (t.isNumericLiteral(expression)) return String(expression.value);
@@ -164,19 +132,13 @@ function processJSXOpeningElement(element) {
164
132
  return null;
165
133
  }
166
134
  function parseJSXElement(element, fallback) {
167
- const message = element.openingElement.selfClosing ? parseJSXOpeningElement(element.openingElement) : parseJSXContainerElement(element);
135
+ const message = element.openingElement.selfClosing ? parseJSXOpeningElement(element.openingElement, element.leadingComments) : parseJSXContainerElement(element);
168
136
  if (message) return message;
169
137
  if (!fallback) return null;
170
138
  const identifier = takeElementTag(element) ?? AUTO_INCREMENT_IDENTIFIER;
171
139
  if (element.openingElement.selfClosing) return new ElementMessage(identifier, [], element);
172
140
  return new ElementMessage(identifier, [parseJSXElement(t.jsxElement(t.jsxOpeningElement(t.jsxIdentifier("Say"), []), t.jsxClosingElement(t.jsxIdentifier("Say")), element.children), true)], element);
173
141
  }
174
- /**
175
- * Read the tag name off an embedded element and remove the attribute so it
176
- * never reaches the rendered element. Only static strings name a tag; anything
177
- * else is left in place and the element falls back to an auto-incremented
178
- * identifier.
179
- */
180
142
  function takeElementTag(element) {
181
143
  const attributes = element.openingElement.attributes;
182
144
  const index = attributes.findIndex((a) => t.isJSXAttribute(a) && getAttributeNameAsString(a) === TAG_ATTRIBUTE);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saykit/transform-jsx",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "JSX and TSX source transformer for SayKit",
5
5
  "keywords": [
6
6
  "i18n",
@@ -47,8 +47,8 @@
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.8.0",
51
- "@saykit/transform-js": "^0.8.0"
50
+ "@saykit/config": "^0.9.1",
51
+ "@saykit/transform-js": "^0.9.1"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@babel/core": "^7.29.7",