@saykit/transform-js 0.0.0-beta-20260418085054
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/generator.d.mts +7 -0
- package/dist/generator.mjs +26 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.mjs +78 -0
- package/dist/parser.d.mts +11 -0
- package/dist/parser.mjs +125 -0
- package/package.json +64 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CompositeMessage } from "@saykit/config/features/messages";
|
|
2
|
+
import * as t from "@babel/types";
|
|
3
|
+
|
|
4
|
+
//#region src/generator.d.ts
|
|
5
|
+
declare function generateSayCallExpression(message: CompositeMessage): t.CallExpression;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { generateSayCallExpression };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ArgumentMessage, ChoiceMessage, CompositeMessage, ElementMessage } from "@saykit/config/features/messages";
|
|
2
|
+
import * as t from "@babel/types";
|
|
3
|
+
//#region src/generator.ts
|
|
4
|
+
function generateSayCallExpression(message) {
|
|
5
|
+
const id = message.descriptor.id ?? message.toHashString();
|
|
6
|
+
const children = generateChildExpressions(message.children);
|
|
7
|
+
const properties = t.objectExpression([t.objectProperty(t.identifier("id"), t.stringLiteral(id)), ...children.map(([ident, expr]) => t.objectProperty(t.identifier(ident), expr))]);
|
|
8
|
+
return t.callExpression(t.memberExpression(message.accessor, t.identifier("call")), [properties]);
|
|
9
|
+
}
|
|
10
|
+
function generateChildExpressions(messages) {
|
|
11
|
+
return messages.reduce((c, m) => {
|
|
12
|
+
if (m instanceof ArgumentMessage) c.push([String(m.identifier), m.expression]);
|
|
13
|
+
if (m instanceof ElementMessage) {
|
|
14
|
+
c.push([String(m.identifier), m.expression]);
|
|
15
|
+
c.push(...generateChildExpressions(m.children));
|
|
16
|
+
}
|
|
17
|
+
if (m instanceof ChoiceMessage) {
|
|
18
|
+
c.push([String(m.identifier), m.expression]);
|
|
19
|
+
c.push(...generateChildExpressions(m.branches.map((b) => b.value)));
|
|
20
|
+
}
|
|
21
|
+
if (m instanceof CompositeMessage) c.push(...generateChildExpressions(m.children));
|
|
22
|
+
return c;
|
|
23
|
+
}, []);
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { generateSayCallExpression };
|
package/dist/index.d.mts
ADDED
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { generateSayCallExpression } from "./generator.mjs";
|
|
2
|
+
import { parseExpression } from "./parser.mjs";
|
|
3
|
+
import { generate } from "@babel/generator";
|
|
4
|
+
import * as parser from "@babel/parser";
|
|
5
|
+
import traverse_ from "@babel/traverse";
|
|
6
|
+
import { assignSequenceIdentifiers } from "@saykit/config/features/messages";
|
|
7
|
+
//#region src/index.ts
|
|
8
|
+
const traverse = traverse_.default || traverse_;
|
|
9
|
+
function createProgram(code, id) {
|
|
10
|
+
const program = parser.parse(code, {
|
|
11
|
+
sourceType: "module",
|
|
12
|
+
sourceFilename: id,
|
|
13
|
+
plugins: ["typescript"],
|
|
14
|
+
allowImportExportEverywhere: false,
|
|
15
|
+
allowReturnOutsideFunction: true,
|
|
16
|
+
ranges: true,
|
|
17
|
+
attachComment: true,
|
|
18
|
+
tokens: true
|
|
19
|
+
});
|
|
20
|
+
return Object.assign(program, { toString() {
|
|
21
|
+
return generate(program, {
|
|
22
|
+
retainLines: true,
|
|
23
|
+
comments: true,
|
|
24
|
+
compact: false
|
|
25
|
+
}).code;
|
|
26
|
+
} });
|
|
27
|
+
}
|
|
28
|
+
function createJsTransformer() {
|
|
29
|
+
return {
|
|
30
|
+
match(id) {
|
|
31
|
+
return [
|
|
32
|
+
".js",
|
|
33
|
+
".cjs",
|
|
34
|
+
".mjs",
|
|
35
|
+
".ts",
|
|
36
|
+
".mts",
|
|
37
|
+
".cts"
|
|
38
|
+
].some((e) => id.endsWith(e));
|
|
39
|
+
},
|
|
40
|
+
extract(code, id) {
|
|
41
|
+
const program = createProgram(code, id);
|
|
42
|
+
const messages = [];
|
|
43
|
+
traverse(program, { Expression(path) {
|
|
44
|
+
path.node.leadingComments = path.node.leadingComments ?? [];
|
|
45
|
+
const message = parseExpression(path.node);
|
|
46
|
+
if (message) {
|
|
47
|
+
assignSequenceIdentifiers(message, { current: 0 });
|
|
48
|
+
messages.push(message);
|
|
49
|
+
path.skip();
|
|
50
|
+
}
|
|
51
|
+
} });
|
|
52
|
+
return messages.map((message) => ({
|
|
53
|
+
message: message.toICUString(),
|
|
54
|
+
translation: void 0,
|
|
55
|
+
id: message.descriptor.id,
|
|
56
|
+
context: message.descriptor.context,
|
|
57
|
+
comments: message.comments,
|
|
58
|
+
references: message.references
|
|
59
|
+
}));
|
|
60
|
+
},
|
|
61
|
+
transform(code, id) {
|
|
62
|
+
const program = createProgram(code, id);
|
|
63
|
+
traverse(program, { Expression(path) {
|
|
64
|
+
path.node.leadingComments = path.node.leadingComments ?? [];
|
|
65
|
+
const message = parseExpression(path.node);
|
|
66
|
+
if (message) {
|
|
67
|
+
assignSequenceIdentifiers(message, { current: 0 });
|
|
68
|
+
const replacement = generateSayCallExpression(message);
|
|
69
|
+
path.replaceWith(replacement);
|
|
70
|
+
path.skip();
|
|
71
|
+
}
|
|
72
|
+
} });
|
|
73
|
+
return program.toString();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { createJsTransformer as default };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ArgumentMessage, CompositeMessage } from "@saykit/config/features/messages";
|
|
2
|
+
import * as t from "@babel/types";
|
|
3
|
+
|
|
4
|
+
//#region src/parser.d.ts
|
|
5
|
+
declare function parseTaggedTemplateExpression(tagged: t.TaggedTemplateExpression): CompositeMessage | null;
|
|
6
|
+
declare function parseCallExpression(call: t.CallExpression): CompositeMessage | null;
|
|
7
|
+
declare function processExpression(expression: t.Node): [t.Expression, t.ObjectExpression | null, string | null] | null;
|
|
8
|
+
declare function parseExpression(expression: t.Expression, fallback?: false): CompositeMessage | null;
|
|
9
|
+
declare function parseExpression(expression: t.Expression, fallback: true): CompositeMessage | ArgumentMessage;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { parseCallExpression, parseExpression, parseTaggedTemplateExpression, processExpression };
|
package/dist/parser.mjs
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { AUTO_INCREMENT_IDENTIFIER, ArgumentMessage, ChoiceMessage, CompositeMessage, LiteralMessage } from "@saykit/config/features/messages";
|
|
2
|
+
import * as t from "@babel/types";
|
|
3
|
+
//#region src/parser.ts
|
|
4
|
+
function parseTaggedTemplateExpression(tagged) {
|
|
5
|
+
const processed = processExpression(tagged.tag);
|
|
6
|
+
if (!processed) return null;
|
|
7
|
+
const [accessor, descriptor] = processed;
|
|
8
|
+
const children = tagged.quasi.quasis.reduce((c, q, i) => {
|
|
9
|
+
c.push(new LiteralMessage(q.value.cooked ?? q.value.raw));
|
|
10
|
+
if (t.isExpression(tagged.quasi.expressions[i])) c.push(parseExpression(tagged.quasi.expressions[i], true));
|
|
11
|
+
return c;
|
|
12
|
+
}, []);
|
|
13
|
+
return new CompositeMessage({
|
|
14
|
+
id: descriptor ? findPropertyValueIfStringLiteralAsString(descriptor, "id") : void 0,
|
|
15
|
+
context: descriptor ? findPropertyValueIfStringLiteralAsString(descriptor, "context") : void 0
|
|
16
|
+
}, getTranslatorComments(tagged.leadingComments ?? []), tagged.loc ? [`${tagged.loc.filename}:${tagged.loc.start.line}`] : [], children, accessor);
|
|
17
|
+
}
|
|
18
|
+
function parseCallExpression(call) {
|
|
19
|
+
const processed = processExpression(call.callee);
|
|
20
|
+
if (!processed) return null;
|
|
21
|
+
const [accessor, descriptor, kind] = processed;
|
|
22
|
+
if (typeof kind === "string" && [
|
|
23
|
+
"select",
|
|
24
|
+
"ordinal",
|
|
25
|
+
"plural"
|
|
26
|
+
].includes(kind)) {
|
|
27
|
+
if (call.arguments.length !== 2) return null;
|
|
28
|
+
if (!t.isExpression(call.arguments[0])) return null;
|
|
29
|
+
if (!t.isObjectExpression(call.arguments[1])) return null;
|
|
30
|
+
const branches = call.arguments[1].properties.reduce((c, p) => {
|
|
31
|
+
if (!t.isObjectProperty(p) || !t.isExpression(p.value)) return c;
|
|
32
|
+
let message = null;
|
|
33
|
+
if (!message && t.isStringLiteral(p.value)) message = new LiteralMessage(p.value.value);
|
|
34
|
+
if (!message) message = parseExpression(p.value, true);
|
|
35
|
+
c.push({
|
|
36
|
+
identifier: getPropertyNameAsString(p.key),
|
|
37
|
+
value: message
|
|
38
|
+
});
|
|
39
|
+
return c;
|
|
40
|
+
}, []);
|
|
41
|
+
const value = call.arguments[0];
|
|
42
|
+
const choice = new ChoiceMessage(kind, getExpressionAsKey(value), branches, value);
|
|
43
|
+
return new CompositeMessage({
|
|
44
|
+
id: descriptor ? findPropertyValueIfStringLiteralAsString(descriptor, "id") : void 0,
|
|
45
|
+
context: descriptor ? findPropertyValueIfStringLiteralAsString(descriptor, "context") : void 0
|
|
46
|
+
}, [], call.loc ? [`${call.loc.filename}:${call.loc.start.line}`] : [], [choice], accessor);
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
function processExpression(expression) {
|
|
51
|
+
if (t.isIdentifier(expression) && expression.name === "say") return [
|
|
52
|
+
expression,
|
|
53
|
+
null,
|
|
54
|
+
null
|
|
55
|
+
];
|
|
56
|
+
if (t.isCallExpression(expression)) {
|
|
57
|
+
const inner = processExpression(expression.callee);
|
|
58
|
+
if (inner && expression.arguments.length === 1 && t.isObjectExpression(expression.arguments[0])) return [
|
|
59
|
+
inner[0],
|
|
60
|
+
expression.arguments[0],
|
|
61
|
+
null
|
|
62
|
+
];
|
|
63
|
+
else if (inner) return [
|
|
64
|
+
inner[0],
|
|
65
|
+
null,
|
|
66
|
+
null
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
if (t.isMemberExpression(expression)) {
|
|
70
|
+
const innerProperty = processExpression(expression.property);
|
|
71
|
+
if (innerProperty) return [
|
|
72
|
+
expression,
|
|
73
|
+
innerProperty[1],
|
|
74
|
+
null
|
|
75
|
+
];
|
|
76
|
+
const innerObject = processExpression(expression.object);
|
|
77
|
+
if (innerObject && t.isIdentifier(expression.property)) return [
|
|
78
|
+
innerObject[0],
|
|
79
|
+
innerObject[1],
|
|
80
|
+
expression.property.name
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
function getExpressionAsKey(node) {
|
|
86
|
+
if (t.isIdentifier(node)) return node.name;
|
|
87
|
+
if (t.isJSXIdentifier(node)) return node.name;
|
|
88
|
+
return AUTO_INCREMENT_IDENTIFIER;
|
|
89
|
+
}
|
|
90
|
+
function parseExpression(expression, fallback) {
|
|
91
|
+
let message = null;
|
|
92
|
+
switch (true) {
|
|
93
|
+
case t.isTaggedTemplateExpression(expression):
|
|
94
|
+
message = parseTaggedTemplateExpression(expression);
|
|
95
|
+
break;
|
|
96
|
+
case t.isCallExpression(expression):
|
|
97
|
+
message = parseCallExpression(expression);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
if (message) return message;
|
|
101
|
+
else if (fallback) return new ArgumentMessage(getExpressionAsKey(expression), expression);
|
|
102
|
+
else return null;
|
|
103
|
+
}
|
|
104
|
+
function getPropertyNameAsString(key) {
|
|
105
|
+
if (t.isIdentifier(key)) return key.name;
|
|
106
|
+
if (t.isStringLiteral(key)) return key.value;
|
|
107
|
+
if (t.isNumericLiteral(key)) return key.value.toString();
|
|
108
|
+
if (t.isBigIntLiteral(key)) return key.value.toString();
|
|
109
|
+
return AUTO_INCREMENT_IDENTIFIER;
|
|
110
|
+
}
|
|
111
|
+
function findPropertyValueIfStringLiteralAsString(object, key) {
|
|
112
|
+
for (const property of object.properties) {
|
|
113
|
+
if (!t.isObjectProperty(property)) continue;
|
|
114
|
+
if (t.isIdentifier(property.key) && property.key.name === key && t.isStringLiteral(property.value)) return property.value.value;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function getTranslatorComments(comments) {
|
|
118
|
+
return comments.reduce((a, c) => {
|
|
119
|
+
const text = c.value.trim();
|
|
120
|
+
if (text.toLowerCase().startsWith("translators:")) a.push(text.slice(12).trim());
|
|
121
|
+
return a;
|
|
122
|
+
}, []);
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
export { parseCallExpression, parseExpression, parseTaggedTemplateExpression, processExpression };
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@saykit/transform-js",
|
|
3
|
+
"version": "0.0.0-beta-20260418085054",
|
|
4
|
+
"description": "JavaScript and TypeScript transformer for SayKit",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"i18n",
|
|
7
|
+
"javascript",
|
|
8
|
+
"saykit",
|
|
9
|
+
"transformer",
|
|
10
|
+
"typescript"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/k0d13/saykit#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/k0d13/saykit/issues"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/k0d13/saykit.git",
|
|
20
|
+
"directory": "packages/transform-js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"!dist/**/*.map"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.mts",
|
|
30
|
+
"default": "./dist/index.mjs"
|
|
31
|
+
},
|
|
32
|
+
"./parser": {
|
|
33
|
+
"types": "./dist/parser.d.mts",
|
|
34
|
+
"default": "./dist/parser.mjs"
|
|
35
|
+
},
|
|
36
|
+
"./generator": {
|
|
37
|
+
"types": "./dist/generator.d.mts",
|
|
38
|
+
"default": "./dist/generator.mjs"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"provenance": true
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@babel/generator": "^7.29.1",
|
|
47
|
+
"@babel/parser": "^7.29.2",
|
|
48
|
+
"@babel/traverse": "^7.29.0",
|
|
49
|
+
"@babel/types": "^7.29.0",
|
|
50
|
+
"@saykit/config": "^0.0.0-beta-20260418085054"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@babel/core": "^7.29.0",
|
|
54
|
+
"@types/babel__core": "^7.20.5",
|
|
55
|
+
"@types/babel__generator": "^7.27.0",
|
|
56
|
+
"@types/babel__traverse": "^7.28.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"check": "tsc --noEmit",
|
|
60
|
+
"test": "vitest",
|
|
61
|
+
"coverage": "vitest run --coverage",
|
|
62
|
+
"build": "tsdown"
|
|
63
|
+
}
|
|
64
|
+
}
|