wuchale 0.17.5 → 0.18.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.
Files changed (38) hide show
  1. package/dist/adapter-utils/index.d.ts +2 -3
  2. package/dist/adapter-utils/index.js +7 -18
  3. package/dist/adapter-utils/mixed-visitor.d.ts +2 -2
  4. package/dist/adapter-utils/mixed-visitor.js +11 -9
  5. package/dist/adapter-vanilla/index.d.ts +7 -2
  6. package/dist/adapter-vanilla/index.js +18 -27
  7. package/dist/adapter-vanilla/transformer.d.ts +13 -11
  8. package/dist/adapter-vanilla/transformer.js +119 -46
  9. package/dist/adapters.d.ts +31 -17
  10. package/dist/adapters.js +66 -31
  11. package/dist/cli/extract.d.ts +1 -4
  12. package/dist/cli/extract.js +4 -53
  13. package/dist/cli/index.js +1 -6
  14. package/dist/cli/status.js +3 -3
  15. package/dist/config.js +3 -0
  16. package/dist/handler.d.ts +26 -19
  17. package/dist/handler.js +546 -271
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.js +1 -1
  20. package/dist/load-utils/index.d.ts +7 -7
  21. package/dist/load-utils/index.js +7 -11
  22. package/dist/load-utils/pure.d.ts +2 -2
  23. package/dist/load-utils/server.d.ts +3 -3
  24. package/dist/load-utils/server.js +14 -12
  25. package/dist/runtime.d.ts +12 -18
  26. package/dist/runtime.js +38 -36
  27. package/dist/url.d.ts +19 -0
  28. package/dist/url.js +57 -0
  29. package/package.json +9 -5
  30. package/src/adapter-vanilla/loaders/bundle.js +4 -2
  31. package/src/adapter-vanilla/loaders/server.js +6 -3
  32. package/src/adapter-vanilla/loaders/vite.js +5 -5
  33. package/src/adapter-vanilla/loaders/vite.ssr.js +6 -7
  34. package/dist/cli/init.d.ts +0 -2
  35. package/dist/cli/init.js +0 -62
  36. package/dist/cli/input.d.ts +0 -2
  37. package/dist/cli/input.js +0 -56
  38. package/src/virtual.d.ts +0 -20
@@ -1,8 +1,8 @@
1
1
  export { MixedVisitor } from './mixed-visitor.js';
2
+ import type { HeuristicResultChecked } from '../adapters.js';
2
3
  export declare const varNames: {
3
4
  rt: string;
4
5
  hmrUpdate: string;
5
- rtWrap: string;
6
6
  };
7
7
  export declare function runtimeVars(wrapFunc: (expr: string) => string, base?: string): {
8
8
  rtTrans: string;
@@ -17,12 +17,11 @@ export declare function runtimeVars(wrapFunc: (expr: string) => string, base?: s
17
17
  };
18
18
  export type RuntimeVars = ReturnType<typeof runtimeVars>;
19
19
  export declare function nonWhitespaceText(msgStr: string): [number, string, number];
20
- export declare function getDependencies(): Promise<Set<string>>;
21
20
  export declare function loaderPathResolver(importMetaUrl: string, baseDir: string, ext: string): (name: string) => string;
22
21
  export declare const commentPrefix = "@wc-";
23
22
  export type CommentDirectives = {
24
23
  ignoreFile?: boolean;
25
- forceInclude?: boolean;
24
+ forceType?: HeuristicResultChecked;
26
25
  context?: string;
27
26
  };
28
27
  export declare function processCommentDirectives(data: string, current: CommentDirectives): CommentDirectives;
@@ -1,18 +1,16 @@
1
- import { readFile } from "node:fs/promises";
2
1
  export { MixedVisitor } from './mixed-visitor.js';
3
2
  import { dirname, resolve } from 'node:path';
4
3
  import { fileURLToPath } from 'node:url';
5
4
  export const varNames = {
6
5
  rt: '_w_runtime_',
7
6
  hmrUpdate: '_w_hmrUpdate_',
8
- rtWrap: '_w_to_rt_',
9
7
  };
10
8
  export function runtimeVars(wrapFunc, base = varNames.rt) {
11
9
  return {
12
10
  rtTrans: `${wrapFunc(base)}.t`,
13
11
  rtTPlural: `${wrapFunc(base)}.tp`,
14
12
  rtPlural: `${wrapFunc(base)}._.p`,
15
- rtLocale: `${wrapFunc(base)}._.l`,
13
+ rtLocale: `${wrapFunc(base)}.l`,
16
14
  rtCtx: `${wrapFunc(base)}.cx`,
17
15
  rtTransCtx: `${wrapFunc(base)}.tx`,
18
16
  rtTransTag: `${wrapFunc(base)}.tt`,
@@ -27,19 +25,6 @@ export function nonWhitespaceText(msgStr) {
27
25
  const endWh = trimmedS.length - trimmed.length;
28
26
  return [startWh, trimmed, endWh];
29
27
  }
30
- export async function getDependencies() {
31
- let json = { devDependencies: {}, dependencies: {} };
32
- try {
33
- const pkgJson = await readFile('package.json');
34
- json = JSON.parse(pkgJson.toString());
35
- }
36
- catch (err) {
37
- if (err.code !== 'ENOENT') {
38
- throw err;
39
- }
40
- }
41
- return new Set(Object.keys({ ...json.devDependencies, ...json.dependencies }));
42
- }
43
28
  export function loaderPathResolver(importMetaUrl, baseDir, ext) {
44
29
  const dir = dirname(fileURLToPath(importMetaUrl));
45
30
  return (name) => resolve(dir, `${baseDir}/${name}.${ext}`);
@@ -49,15 +34,19 @@ const commentDirectives = {
49
34
  ignore: `${commentPrefix}ignore`,
50
35
  ignoreFile: `${commentPrefix}ignore-file`,
51
36
  include: `${commentPrefix}include`,
37
+ url: `${commentPrefix}url`,
52
38
  context: `${commentPrefix}context:`,
53
39
  };
54
40
  export function processCommentDirectives(data, current) {
55
41
  const directives = { ...current };
56
42
  if (data === commentDirectives.ignore) {
57
- directives.forceInclude = false;
43
+ directives.forceType = false;
58
44
  }
59
45
  if (data === commentDirectives.include) {
60
- directives.forceInclude = true;
46
+ directives.forceType = 'message';
47
+ }
48
+ if (data === commentDirectives.url) {
49
+ directives.forceType = 'url';
61
50
  }
62
51
  if (data === commentDirectives.ignoreFile) {
63
52
  directives.ignoreFile = true;
@@ -1,5 +1,5 @@
1
1
  import type MagicString from "magic-string";
2
- import { IndexTracker, Message, type HeuristicDetails, type HeuristicDetailsBase, type HeuristicFunc } from "../adapters.js";
2
+ import { IndexTracker, Message, type HeuristicDetails, type HeuristicDetailsBase, type HeuristicFunc, type MessageType } from "../adapters.js";
3
3
  import { type RuntimeVars, type CommentDirectives } from "./index.js";
4
4
  type NestedRanges = [number, number, boolean][];
5
5
  type InitProps<NodeT> = {
@@ -37,7 +37,7 @@ export interface MixedVisitor<NodeT> extends InitProps<NodeT> {
37
37
  }
38
38
  export declare class MixedVisitor<NodeT> {
39
39
  constructor(props: InitProps<NodeT>);
40
- separatelyVisitChildren: (props: VisitProps<NodeT>) => [boolean, boolean, boolean, Message[]];
40
+ separatelyVisitChildren: (props: VisitProps<NodeT>) => [boolean, boolean, boolean, MessageType, Message[]];
41
41
  visit: (props: VisitProps<NodeT>) => Message[];
42
42
  }
43
43
  export {};
@@ -17,7 +17,7 @@ export class MixedVisitor {
17
17
  continue;
18
18
  }
19
19
  hasTextChild = true;
20
- heurStr += strContent + ' ';
20
+ heurStr += strContent;
21
21
  }
22
22
  else if (this.isComment(child)) {
23
23
  if (this.getCommentData(child).trim().startsWith(commentPrefix)) {
@@ -26,7 +26,7 @@ export class MixedVisitor {
26
26
  }
27
27
  else if (!this.leaveInPlace(child)) {
28
28
  hasNonTextChild = true;
29
- heurStr += `# `;
29
+ heurStr += `#`;
30
30
  }
31
31
  }
32
32
  heurStr = heurStr.trimEnd();
@@ -35,11 +35,12 @@ export class MixedVisitor {
35
35
  element: props.element,
36
36
  attribute: props.attribute,
37
37
  }), null);
38
- const passHeuristic = this.checkHeuristic(msg);
39
- let hasCompoundText = hasTextChild && hasNonTextChild;
40
- const visitAsOne = passHeuristic && !hasCommentDirectives;
41
- if (props.inCompoundText || hasCompoundText && visitAsOne) {
42
- return [false, hasTextChild, hasCompoundText, []];
38
+ const heurMsgType = this.checkHeuristic(msg);
39
+ if (heurMsgType) {
40
+ let hasCompoundText = hasTextChild && hasNonTextChild;
41
+ if (props.inCompoundText || hasCompoundText && !hasCommentDirectives) {
42
+ return [false, hasTextChild, hasCompoundText, heurMsgType, []];
43
+ }
43
44
  }
44
45
  // can't be extracted as one; visit each separately if markup
45
46
  const msgs = [];
@@ -48,13 +49,13 @@ export class MixedVisitor {
48
49
  msgs.push(...this.visitFunc(child, props.inCompoundText));
49
50
  }
50
51
  }
51
- return [true, false, false, msgs];
52
+ return [true, false, false, heurMsgType || 'message', msgs];
52
53
  };
53
54
  visit = (props) => {
54
55
  if (props.children.length === 0) {
55
56
  return [];
56
57
  }
57
- const [visitedSeparately, hasTextChild, hasCompoundText, separateTxts] = this.separatelyVisitChildren(props);
58
+ const [visitedSeparately, hasTextChild, hasCompoundText, heurMsgType, separateTxts] = this.separatelyVisitChildren(props);
58
59
  if (visitedSeparately) {
59
60
  return separateTxts;
60
61
  }
@@ -143,6 +144,7 @@ export class MixedVisitor {
143
144
  return msgs;
144
145
  }
145
146
  const msgInfo = new Message(msgStr, this.fullHeuristicDetails({ scope: props.scope }), props.commentDirectives.context);
147
+ msgInfo.type = heurMsgType;
146
148
  msgInfo.comments = comments;
147
149
  if (hasTextChild || hasTextDescendants) {
148
150
  msgs.push(msgInfo);
@@ -1,6 +1,11 @@
1
- import type { AdapterArgs, Adapter, CodePattern } from "../adapters.js";
1
+ import type { AdapterArgs, Adapter, CodePattern, LoaderChoice } from "../adapters.js";
2
2
  import { Transformer } from "./transformer.js";
3
3
  export { Transformer };
4
4
  export { parseScript, scriptParseOptions, scriptParseOptionsWithComments } from './transformer.js';
5
5
  export declare const pluralPattern: CodePattern;
6
- export declare const adapter: (args?: AdapterArgs) => Adapter;
6
+ type LoadersAvailable = 'bundle' | 'server' | 'vite';
7
+ export declare function getDefaultLoaderPath(loader: LoaderChoice<LoadersAvailable>, bundle: boolean): string | {
8
+ client: string;
9
+ server: string;
10
+ };
11
+ export declare const adapter: (args?: AdapterArgs<LoadersAvailable>) => Adapter;
@@ -2,7 +2,7 @@
2
2
  import { defaultGenerateLoadID, defaultHeuristicFuncOnly } from '../adapters.js';
3
3
  import { deepMergeObjects } from "../config.js";
4
4
  import { Transformer } from "./transformer.js";
5
- import { getDependencies, loaderPathResolver } from '../adapter-utils/index.js';
5
+ import { loaderPathResolver } from '../adapter-utils/index.js';
6
6
  export { Transformer };
7
7
  export { parseScript, scriptParseOptions, scriptParseOptionsWithComments } from './transformer.js';
8
8
  export const pluralPattern = {
@@ -11,53 +11,44 @@ export const pluralPattern = {
11
11
  };
12
12
  const defaultArgs = {
13
13
  files: { include: 'src/**/*.{js,ts}', ignore: '**/*.d.ts' },
14
- catalog: './src/locales/{locale}',
14
+ localesDir: './src/locales',
15
15
  patterns: [pluralPattern],
16
16
  heuristic: defaultHeuristicFuncOnly,
17
17
  granularLoad: false,
18
18
  bundleLoad: false,
19
19
  generateLoadID: defaultGenerateLoadID,
20
- writeFiles: {},
20
+ loader: 'vite',
21
21
  runtime: {
22
22
  useReactive: ({ nested }) => ({
23
23
  init: nested ? null : false,
24
24
  use: nested ? null : false,
25
25
  }),
26
26
  plain: {
27
- importName: 'default',
28
27
  wrapInit: expr => expr,
29
28
  wrapUse: expr => expr,
30
29
  }
31
30
  }
32
31
  };
33
32
  const resolveLoaderPath = loaderPathResolver(import.meta.url, '../../src/adapter-vanilla/loaders', 'js');
33
+ export function getDefaultLoaderPath(loader, bundle) {
34
+ if (bundle) {
35
+ return resolveLoaderPath('bundle');
36
+ }
37
+ if (loader === 'vite') {
38
+ return {
39
+ client: resolveLoaderPath('vite'),
40
+ server: resolveLoaderPath('vite.ssr'),
41
+ };
42
+ }
43
+ return resolveLoaderPath(loader);
44
+ }
34
45
  export const adapter = (args = defaultArgs) => {
35
- const { heuristic, patterns, runtime, ...rest } = deepMergeObjects(args, defaultArgs);
46
+ const { heuristic, patterns, runtime, loader, ...rest } = deepMergeObjects(args, defaultArgs);
36
47
  return {
37
- transform: ({ content, filename, index, expr }) => new Transformer(content, filename, index, heuristic, patterns, expr, runtime).transform(),
48
+ transform: ({ content, filename, index, expr, matchUrl }) => new Transformer(content, filename, index, heuristic, patterns, expr, runtime, matchUrl).transform(),
38
49
  loaderExts: ['.js', '.ts'],
39
- defaultLoaders: async () => {
40
- if (rest.bundleLoad) {
41
- return ['bundle'];
42
- }
43
- const deps = await getDependencies();
44
- const available = ['server'];
45
- if (deps.has('vite')) {
46
- available.unshift('vite');
47
- }
48
- return available;
49
- },
50
- defaultLoaderPath: (loader) => {
51
- if (loader === 'vite') {
52
- return {
53
- client: resolveLoaderPath('vite'),
54
- server: resolveLoaderPath('vite.ssr'),
55
- };
56
- }
57
- return resolveLoaderPath(loader);
58
- },
50
+ defaultLoaderPath: getDefaultLoaderPath(loader, rest.bundleLoad),
59
51
  runtime,
60
52
  ...rest,
61
- docsUrl: 'https://wuchale.dev/adapters/vanilla'
62
53
  };
63
54
  };
@@ -1,7 +1,7 @@
1
1
  import MagicString from "magic-string";
2
2
  import type * as Estree from "acorn";
3
3
  import { Message } from '../adapters.js';
4
- import type { HeuristicDetailsBase, HeuristicFunc, IndexTracker, ScriptDeclType, TransformOutput, HeuristicDetails, RuntimeConf, CatalogExpr, CodePattern } from "../adapters.js";
4
+ import type { HeuristicDetailsBase, HeuristicFunc, IndexTracker, ScriptDeclType, TransformOutput, HeuristicDetails, RuntimeConf, CatalogExpr, CodePattern, UrlMatcher, HeuristicResultChecked, MessageType } from "../adapters.js";
5
5
  import { type RuntimeVars, type CommentDirectives } from "../adapter-utils/index.js";
6
6
  export declare const scriptParseOptions: Estree.Options;
7
7
  export declare function scriptParseOptionsWithComments(): [Estree.Options, Estree.Comment[][]];
@@ -15,6 +15,7 @@ export declare class Transformer {
15
15
  filename: string;
16
16
  mstr: MagicString;
17
17
  patterns: CodePattern[];
18
+ matchUrl: UrlMatcher;
18
19
  initRuntime: InitRuntimeFunc;
19
20
  currentRtVar: string;
20
21
  vars: () => RuntimeVars;
@@ -25,16 +26,15 @@ export declare class Transformer {
25
26
  currentFuncDef: string | null;
26
27
  currentCall: string;
27
28
  currentTopLevelCall: string;
29
+ /** .start of the first statements in their respective parents, to put the runtime init before */
30
+ realBodyStarts: Set<number>;
28
31
  /** for subclasses. right now for svelte, if in <script module> */
29
32
  additionalState: object;
30
- constructor(content: string, filename: string, index: IndexTracker, heuristic: HeuristicFunc, patterns: CodePattern[], catalogExpr: CatalogExpr, rtConf: RuntimeConf, rtBaseVars?: string[]);
33
+ constructor(content: string, filename: string, index: IndexTracker, heuristic: HeuristicFunc, patterns: CodePattern[], catalogExpr: CatalogExpr, rtConf: RuntimeConf, matchUrl: (url: string) => string, rtBaseVars?: string[]);
31
34
  fullHeuristicDetails: (detailsBase: HeuristicDetailsBase) => HeuristicDetails;
32
- checkHeuristicBool: (msg: Message) => boolean;
33
- checkHeuristic: (msgStr: string, detailsBase: HeuristicDetailsBase) => [boolean, Message];
34
- visitLiteral: (node: Estree.Literal & {
35
- start: number;
36
- end: number;
37
- }) => Message[];
35
+ getHeuristicMessageType: (msg: Message) => HeuristicResultChecked;
36
+ checkHeuristic: (msgStr: string, detailsBase: HeuristicDetailsBase) => [HeuristicResultChecked, Message];
37
+ visitLiteral: (node: Estree.Literal, heuristicDetailsBase?: HeuristicDetailsBase) => Message[];
38
38
  visitArrayExpression: (node: Estree.ArrayExpression) => Message[];
39
39
  visitSequenceExpression: (node: Estree.SequenceExpression) => Message[];
40
40
  visitObjectExpression: (node: Estree.ObjectExpression) => Message[];
@@ -65,6 +65,7 @@ export declare class Transformer {
65
65
  visitVariableDeclaration: (node: Estree.VariableDeclaration) => Message[];
66
66
  visitExportNamedDeclaration: (node: Estree.ExportNamedDeclaration) => Message[];
67
67
  visitExportDefaultDeclaration: (node: Estree.ExportNamedDeclaration) => Message[];
68
+ visitStatementsNSaveRealBodyStart: (nodes: (Estree.Statement | Estree.ModuleDeclaration)[]) => Message[];
68
69
  getRealBodyStart: (nodes: (Estree.Statement | Estree.ModuleDeclaration)[]) => number | undefined;
69
70
  visitFunctionBody: (node: Estree.BlockStatement | Estree.Expression, name: string | null, end?: number) => Message[];
70
71
  visitFunctionDeclaration: (node: Estree.FunctionDeclaration) => Message[];
@@ -74,11 +75,12 @@ export declare class Transformer {
74
75
  visitReturnStatement: (node: Estree.ReturnStatement) => Message[];
75
76
  visitIfStatement: (node: Estree.IfStatement) => Message[];
76
77
  visitClassDeclaration: (node: Estree.ClassDeclaration) => Message[];
77
- checkHeuristicTemplateLiteral: (node: Estree.TemplateLiteral) => boolean;
78
- visitTemplateLiteralQuasis: (node: Estree.TemplateLiteral) => [number, Message[]];
79
- visitTemplateLiteral: (node: Estree.TemplateLiteral, ignoreHeuristic?: boolean) => Message[];
78
+ checkHeuristicTemplateLiteral: (node: Estree.TemplateLiteral, heurDetails?: HeuristicDetailsBase) => HeuristicResultChecked;
79
+ visitTemplateLiteralQuasis: (node: Estree.TemplateLiteral, msgTyp: MessageType) => [number, Message[]];
80
+ visitTemplateLiteral: (node: Estree.TemplateLiteral, heurDetails?: HeuristicDetailsBase | boolean) => Message[];
80
81
  visitTaggedTemplateExpression: (node: Estree.TaggedTemplateExpression) => Message[];
81
82
  visitProgram: (node: Estree.Program) => Message[];
83
+ visitWithCommentDirectives: (node: Estree.AnyNode, func: Function) => any;
82
84
  visit: (node: Estree.AnyNode) => Message[];
83
85
  finalize: (msgs: Message[], hmrHeaderIndex: number, additionalHeader?: string) => TransformOutput;
84
86
  transform: () => TransformOutput;
@@ -48,6 +48,7 @@ export class Transformer {
48
48
  filename;
49
49
  mstr;
50
50
  patterns;
51
+ matchUrl;
51
52
  initRuntime;
52
53
  currentRtVar;
53
54
  vars;
@@ -59,14 +60,17 @@ export class Transformer {
59
60
  currentFuncDef = null;
60
61
  currentCall;
61
62
  currentTopLevelCall;
63
+ /** .start of the first statements in their respective parents, to put the runtime init before */
64
+ realBodyStarts = new Set();
62
65
  /** for subclasses. right now for svelte, if in <script module> */
63
66
  additionalState = {};
64
- constructor(content, filename, index, heuristic, patterns, catalogExpr, rtConf, rtBaseVars = [varNames.rt]) {
67
+ constructor(content, filename, index, heuristic, patterns, catalogExpr, rtConf, matchUrl, rtBaseVars = [varNames.rt]) {
65
68
  this.index = index;
66
69
  this.heuristic = heuristic;
67
70
  this.patterns = patterns;
68
71
  this.content = content;
69
72
  this.filename = filename;
73
+ this.matchUrl = matchUrl;
70
74
  const topLevelUseReactive = rtConf.useReactive({
71
75
  funcName: null,
72
76
  nested: false,
@@ -99,8 +103,7 @@ export class Transformer {
99
103
  }
100
104
  const wrapInit = useReactive.init ? rtConf.reactive.wrapInit : rtConf.plain.wrapInit;
101
105
  const expr = useReactive.init ? catalogExpr.reactive : catalogExpr.plain;
102
- const runtimeExpr = `${varNames.rtWrap}(${expr})`;
103
- return `\nconst ${this.currentRtVar} = ${wrapInit(runtimeExpr)}\n`;
106
+ return `\nconst ${this.currentRtVar} = ${wrapInit(expr)}\n`;
104
107
  };
105
108
  }
106
109
  fullHeuristicDetails = (detailsBase) => {
@@ -117,17 +120,20 @@ export class Transformer {
117
120
  }
118
121
  return details;
119
122
  };
120
- checkHeuristicBool = (msg) => {
123
+ getHeuristicMessageType = (msg) => {
121
124
  const msgStr = msg.msgStr.join('\n');
122
125
  if (!msgStr) {
123
126
  // nothing to ask
124
127
  return false;
125
128
  }
126
- let extract = this.commentDirectives.forceInclude;
127
- if (extract == null) {
128
- extract = this.heuristic(msg) ?? defaultHeuristicFuncOnly(msg) ?? true;
129
+ if (this.commentDirectives.forceType === false) {
130
+ return false;
131
+ }
132
+ const heuRes = this.heuristic(msg) ?? defaultHeuristicFuncOnly(msg) ?? 'message';
133
+ if (this.commentDirectives.forceType == null && heuRes === 'url' && this.matchUrl(msgStr) == null) {
134
+ return false;
129
135
  }
130
- return extract;
136
+ return this.commentDirectives.forceType || heuRes;
131
137
  };
132
138
  checkHeuristic = (msgStr, detailsBase) => {
133
139
  if (!msgStr) {
@@ -135,14 +141,18 @@ export class Transformer {
135
141
  return [false, null];
136
142
  }
137
143
  const msg = new Message(msgStr, this.fullHeuristicDetails(detailsBase), this.commentDirectives.context);
138
- return [this.checkHeuristicBool(msg), msg];
144
+ const heuRes = this.getHeuristicMessageType(msg);
145
+ if (heuRes) {
146
+ msg.type = heuRes;
147
+ }
148
+ return [heuRes, msg];
139
149
  };
140
- visitLiteral = (node) => {
150
+ visitLiteral = (node, heuristicDetailsBase) => {
141
151
  if (typeof node.value !== 'string') {
142
152
  return [];
143
153
  }
144
154
  const { start, end } = node;
145
- const [pass, msgInfo] = this.checkHeuristic(node.value, { scope: 'script' });
155
+ const [pass, msgInfo] = this.checkHeuristic(node.value, heuristicDetailsBase ?? { scope: 'script' });
146
156
  if (!pass) {
147
157
  return [];
148
158
  }
@@ -189,29 +199,53 @@ export class Transformer {
189
199
  if (!pattern) {
190
200
  return this.defaultVisitCallExpression(node);
191
201
  }
202
+ let iLastNonOther = pattern.args.length - 1; // after this no change will be made
203
+ for (; iLastNonOther >= 0; iLastNonOther--) {
204
+ if (pattern.args[iLastNonOther] !== 'other') {
205
+ break;
206
+ }
207
+ }
192
208
  const msgs = [];
209
+ const updates = [];
210
+ const appends = [];
193
211
  let lastArgEnd;
194
212
  for (const [i, arg] of pattern.args.entries()) {
195
213
  const argVal = node.arguments[i];
196
214
  let argInsertIndex;
197
215
  if (argVal == null) {
216
+ argInsertIndex = lastArgEnd ?? node.callee.end + 1;
198
217
  if (lastArgEnd == null) {
199
- return this.defaultVisitCallExpression(node);
218
+ lastArgEnd = argInsertIndex;
200
219
  }
201
- argInsertIndex = lastArgEnd;
202
220
  }
203
221
  else {
204
222
  lastArgEnd = argVal.end;
205
223
  }
224
+ const comma = i > 0 ? ', ' : '';
206
225
  if (arg === 'other') {
226
+ if (argVal == null && i < iLastNonOther) {
227
+ appends.push([argInsertIndex, `${comma}undefined`]);
228
+ }
229
+ continue;
230
+ }
231
+ if (arg === 'locale') {
232
+ if (argVal) {
233
+ if (argVal.type !== 'Literal' || typeof argVal.value !== 'string') {
234
+ continue;
235
+ }
236
+ updates.push([argVal.start, argVal.end, this.vars().rtLocale]);
237
+ }
238
+ else {
239
+ appends.push([argInsertIndex, `${comma}${this.vars().rtLocale}`]);
240
+ }
207
241
  continue;
208
242
  }
209
243
  if (arg === 'pluralFunc') {
210
244
  if (argVal) {
211
- this.mstr.update(argVal.start, argVal.end, this.vars().rtPlural);
245
+ updates.push([argVal.start, argVal.end, this.vars().rtPlural]);
212
246
  }
213
247
  else {
214
- this.mstr.appendRight(argInsertIndex, `, ${this.vars().rtPlural}`);
248
+ appends.push([argInsertIndex, `${comma}${this.vars().rtPlural}`]);
215
249
  }
216
250
  continue;
217
251
  }
@@ -224,7 +258,7 @@ export class Transformer {
224
258
  return this.defaultVisitCallExpression(node);
225
259
  }
226
260
  const msgInfo = new Message(argVal.value, this.fullHeuristicDetails({ scope: 'script' }), this.commentDirectives.context);
227
- this.mstr.update(argVal.start, argVal.end, `${this.vars().rtTrans}(${this.index.get(msgInfo.toKey())})`);
261
+ updates.push([argVal.start, argVal.end, `${this.vars().rtTrans}(${this.index.get(msgInfo.toKey())})`]);
228
262
  msgs.push(msgInfo);
229
263
  continue;
230
264
  }
@@ -247,7 +281,13 @@ export class Transformer {
247
281
  msgInfo.plural = true;
248
282
  const index = this.index.get(msgInfo.toKey());
249
283
  msgs.push(msgInfo);
250
- this.mstr.update(argVal.start, argVal.end, `${this.vars().rtTPlural}(${index})`);
284
+ updates.push([argVal.start, argVal.end, `${this.vars().rtTPlural}(${index})`]);
285
+ }
286
+ for (const [start, end, by] of updates) {
287
+ this.mstr.update(start, end, by);
288
+ }
289
+ for (const [index, insert] of appends) {
290
+ this.mstr.appendRight(index, insert);
251
291
  }
252
292
  return msgs;
253
293
  };
@@ -348,14 +388,40 @@ export class Transformer {
348
388
  visitVariableDeclaration = (node) => node.declarations.map(this.visitVariableDeclarator).flat();
349
389
  visitExportNamedDeclaration = (node) => node.declaration ? this.visit(node.declaration) : [];
350
390
  visitExportDefaultDeclaration = this.visitExportNamedDeclaration;
391
+ visitStatementsNSaveRealBodyStart = (nodes) => {
392
+ const msgs = [];
393
+ let bodyStart = null;
394
+ for (const bod of nodes) {
395
+ let currentContent;
396
+ if (bodyStart == null) {
397
+ currentContent = this.mstr.toString();
398
+ }
399
+ const msgsBod = this.visit(bod);
400
+ if (bodyStart == null && this.mstr.toString() !== currentContent) {
401
+ bodyStart = bod.start;
402
+ }
403
+ if (msgsBod.length) {
404
+ msgs.push(...msgsBod);
405
+ }
406
+ }
407
+ if (bodyStart) {
408
+ this.realBodyStarts.add(bodyStart);
409
+ }
410
+ return msgs;
411
+ };
351
412
  getRealBodyStart = (nodes) => {
413
+ let nonLiteralStart = null;
352
414
  for (const node of nodes) {
353
- if (node.type === 'ExpressionStatement' && node.expression.type === 'Literal') {
354
- continue;
415
+ if (this.realBodyStarts.has(node.start)) {
416
+ return node.start;
417
+ }
418
+ if (nonLiteralStart == null
419
+ && node.type !== 'ImportDeclaration'
420
+ && (node.type !== 'ExpressionStatement' || node.expression.type !== 'Literal')) {
421
+ nonLiteralStart = node.start;
355
422
  }
356
- return node.start;
357
423
  }
358
- return nodes[0]?.start;
424
+ return nonLiteralStart ?? nodes[0]?.start;
359
425
  };
360
426
  visitFunctionBody = (node, name, end) => {
361
427
  const prevFuncDef = this.currentFuncDef;
@@ -400,7 +466,7 @@ export class Transformer {
400
466
  };
401
467
  visitArrowFunctionExpression = (node) => this.visitFunctionBody(node.body, '', node.end);
402
468
  visitFunctionExpression = (node) => this.visitFunctionBody(node.body, '');
403
- visitBlockStatement = (node) => node.body.map(this.visit).flat();
469
+ visitBlockStatement = (node) => this.visitStatementsNSaveRealBodyStart(node.body);
404
470
  visitReturnStatement = (node) => node.argument ? this.visit(node.argument) : [];
405
471
  visitIfStatement = (node) => {
406
472
  const msgs = this.visit(node.test);
@@ -430,7 +496,7 @@ export class Transformer {
430
496
  this.declaring = prevDecl; // restore
431
497
  return msgs;
432
498
  };
433
- checkHeuristicTemplateLiteral = (node) => {
499
+ checkHeuristicTemplateLiteral = (node, heurDetails) => {
434
500
  let heurTxt = '';
435
501
  for (const quasi of node.quasis) {
436
502
  heurTxt += quasi.value.cooked ?? '';
@@ -439,10 +505,10 @@ export class Transformer {
439
505
  }
440
506
  }
441
507
  heurTxt = heurTxt.trim();
442
- const [pass] = this.checkHeuristic(heurTxt, { scope: 'script' });
508
+ const [pass] = this.checkHeuristic(heurTxt, heurDetails ?? { scope: 'script' });
443
509
  return pass;
444
510
  };
445
- visitTemplateLiteralQuasis = (node) => {
511
+ visitTemplateLiteralQuasis = (node, msgTyp) => {
446
512
  const msgs = [];
447
513
  let msgStr = node.quasis[0].value?.cooked ?? '';
448
514
  const comments = [];
@@ -460,18 +526,22 @@ export class Transformer {
460
526
  this.mstr.update(end, end + 2, ', ');
461
527
  }
462
528
  const msgInfo = new Message(msgStr, this.fullHeuristicDetails({ scope: 'script' }), this.commentDirectives.context);
529
+ msgInfo.type = msgTyp;
463
530
  msgInfo.comments = comments;
464
531
  const index = this.index.get(msgInfo.toKey());
465
532
  msgs.push(msgInfo);
466
533
  return [index, msgs];
467
534
  };
468
- visitTemplateLiteral = (node, ignoreHeuristic = false) => {
469
- if (!ignoreHeuristic) {
470
- if (!this.checkHeuristicTemplateLiteral(node)) {
535
+ visitTemplateLiteral = (node, heurDetails = false) => {
536
+ let msgTyp = 'message';
537
+ if (heurDetails !== true) {
538
+ const heuRes = this.checkHeuristicTemplateLiteral(node, typeof heurDetails === 'boolean' ? undefined : heurDetails);
539
+ if (!heuRes) {
471
540
  return node.expressions.map(this.visit).flat();
472
541
  }
542
+ msgTyp = heuRes;
473
543
  }
474
- const [index, msgs] = this.visitTemplateLiteralQuasis(node);
544
+ const [index, msgs] = this.visitTemplateLiteralQuasis(node, msgTyp);
475
545
  const { start: start0, end: end0 } = node.quasis[0];
476
546
  let begin = `${this.vars().rtTrans}(${index}`;
477
547
  let end = ')';
@@ -491,8 +561,9 @@ export class Transformer {
491
561
  const prevCall = this.currentCall;
492
562
  this.currentCall = this.getCalleeName(node.tag);
493
563
  let msgs = [];
494
- if (this.checkHeuristicTemplateLiteral(node.quasi)) {
495
- const [index, msgsNew] = this.visitTemplateLiteralQuasis(node.quasi);
564
+ const heuRes = this.checkHeuristicTemplateLiteral(node.quasi);
565
+ if (heuRes) {
566
+ const [index, msgsNew] = this.visitTemplateLiteralQuasis(node.quasi, heuRes);
496
567
  msgs = msgsNew;
497
568
  this.mstr.appendRight(node.tag.start, `${this.vars().rtTransTag}(`);
498
569
  const { start, end, expressions } = node.quasi;
@@ -509,17 +580,14 @@ export class Transformer {
509
580
  return msgs;
510
581
  };
511
582
  visitProgram = (node) => {
512
- const msgs = [];
513
583
  this.insideProgram = true;
514
- for (const child of node.body) {
515
- msgs.push(...this.visit(child));
516
- }
584
+ const msgs = this.visitStatementsNSaveRealBodyStart(node.body);
517
585
  this.insideProgram = false;
518
586
  return msgs;
519
587
  };
520
- visit = (node) => {
521
- // for estree
588
+ visitWithCommentDirectives = (node, func) => {
522
589
  const commentDirectives = { ...this.commentDirectives };
590
+ // for estree
523
591
  const comments = this.comments[node.start];
524
592
  // @ts-expect-error
525
593
  for (const comment of node.leadingComments ?? comments ?? []) {
@@ -528,18 +596,23 @@ export class Transformer {
528
596
  if (this.commentDirectives.ignoreFile) {
529
597
  return [];
530
598
  }
599
+ const res = func();
600
+ this.commentDirectives = commentDirectives; // restore
601
+ return res;
602
+ };
603
+ visit = (node) => this.visitWithCommentDirectives(node, () => {
604
+ if (this.commentDirectives.forceType === false) {
605
+ return [];
606
+ }
531
607
  let msgs = [];
532
- if (this.commentDirectives.forceInclude !== false) {
533
- const methodName = `visit${node.type}`;
534
- if (methodName in this) {
535
- msgs = this[methodName](node);
536
- // } else {
537
- // console.log(node)
538
- }
608
+ const methodName = `visit${node.type}`;
609
+ if (methodName in this) {
610
+ msgs = this[methodName](node);
611
+ // } else {
612
+ // console.log(node)
539
613
  }
540
- this.commentDirectives = commentDirectives; // restore
541
614
  return msgs;
542
- };
615
+ });
543
616
  finalize = (msgs, hmrHeaderIndex, additionalHeader = '') => ({
544
617
  msgs,
545
618
  output: header => {