@cloudcare/hvigor-ohos-plugin 0.1.1 → 0.1.2-alpha.2

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.
@@ -5,21 +5,47 @@
5
5
  * the compiler's in-memory source. Application files are never modified.
6
6
  */
7
7
 
8
- declare const require: {
9
- main: {
10
- require(moduleName: string): any;
11
- };
12
- };
8
+ import { hasRuntimeDependency, objectProperty, SourceDiagnosticReporter, SourceTransformDiagnostic,
9
+ parseApplicationSource, reportSourceDiagnostic, reportSourceParseError } from './ft-transform-context';
10
+ import { createLexicalResolver } from './ft-lexical-scope';
11
+ import { allocateName as selectIdentifierName, collectIdentifierNames } from './ft-generated-names';
12
+
13
+ declare const require: any;
13
14
  declare const __dirname: string;
14
15
 
15
16
  type TypeScriptApi = any;
16
- type AbilityClassKind = 'stage' | 'ui' | 'other';
17
+ type AbilityClassKind = 'stage' | 'ui' | 'other' | 'intent' | 'legacyIntent';
18
+ type SourceReader = (fileName: string) => string | undefined;
19
+
20
+ interface AbilityClassInfo {
21
+ kind: AbilityClassKind | null;
22
+ applicationBase: boolean;
23
+ base?: any;
24
+ }
25
+
26
+ interface AbilityBase {
27
+ kind: AbilityClassKind | null;
28
+ declaration?: any;
29
+ }
30
+
31
+ interface LaunchParameterTypes {
32
+ want: string;
33
+ abilityConstant: string;
34
+ needed: boolean;
35
+ }
17
36
 
18
37
  interface ColdLaunchInjection {
19
38
  position: number;
39
+ end?: number;
20
40
  content: string;
21
41
  }
22
42
 
43
+ interface ColdLaunchInjections {
44
+ injections: ColdLaunchInjection[];
45
+ hasUIAbility: boolean;
46
+ hasAbility: boolean;
47
+ }
48
+
23
49
  const COLD_LAUNCH_BRIDGE_IMPORT = '@guancecloud/ft_sdk/Index';
24
50
  const COLD_LAUNCH_TRANSFORM_MARKER = '/* guance-cold-launch-transform:instrumented */';
25
51
 
@@ -35,27 +61,6 @@ function visit(ts: TypeScriptApi, node: any, visitor: (current: any) => void): v
35
61
  ts.forEachChild(node, (child: any) => visit(ts, child, visitor));
36
62
  }
37
63
 
38
- function collectIdentifierNames(ts: TypeScriptApi, sourceFile: any): Set<string> {
39
- const identifiers = new Set<string>();
40
- visit(ts, sourceFile, (node: any) => {
41
- if (ts.isIdentifier(node)) {
42
- identifiers.add(node.text);
43
- }
44
- });
45
- return identifiers;
46
- }
47
-
48
- function selectBridgeName(identifiers: Set<string>): string {
49
- const baseName = '__ftColdLaunchBridge';
50
- let bridgeName = baseName;
51
- let suffix = 1;
52
- while (identifiers.has(bridgeName)) {
53
- bridgeName = `${baseName}${suffix}`;
54
- suffix++;
55
- }
56
- return bridgeName;
57
- }
58
-
59
64
  function getPluginVersion(): string {
60
65
  try {
61
66
  const fileSystem = require.main.require('fs');
@@ -82,7 +87,88 @@ function findImportInsertionOffset(ts: TypeScriptApi, sourceFile: any): number {
82
87
  return offset;
83
88
  }
84
89
 
90
+ /** Only opt in when a readable HAP manifest explicitly has no module entry. */
91
+ function needsUIAbilityModuleFallback(ts: TypeScriptApi, id: string, readSource?: SourceReader): boolean {
92
+ const normalized = id.replace(/\\/g, '/');
93
+ const sourceIndex = normalized.lastIndexOf('/src/main/ets/');
94
+ if (sourceIndex < 0) {
95
+ return false;
96
+ }
97
+ const manifestPath = normalized.slice(0, sourceIndex) + '/src/main/module.json5';
98
+ try {
99
+ const nodeRequire = require.main && typeof require.main.require === 'function'
100
+ ? require.main.require.bind(require.main) : require;
101
+ const code = readSource ? readSource(manifestPath) : nodeRequire('fs').readFileSync(manifestPath, 'utf8');
102
+ if (typeof code !== 'string') {
103
+ return false;
104
+ }
105
+ // Parsing an object initializer accepts JSON5 comments, single quotes and
106
+ // unquoted keys without mistaking nested ability srcEntry fields for Stage.
107
+ const parsed = ts.createSourceFile(manifestPath, `const __manifest = ${code}`,
108
+ ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
109
+ if (parsed.parseDiagnostics.length > 0 || parsed.statements.length !== 1 ||
110
+ !ts.isVariableStatement(parsed.statements[0])) {
111
+ return false;
112
+ }
113
+ const root = parsed.statements[0].declarationList.declarations[0].initializer;
114
+ const property = (object: any, name: string): any | undefined => {
115
+ if (!object || !ts.isObjectLiteralExpression(object)) {
116
+ return undefined;
117
+ }
118
+ const matches = object.properties.filter((item: any) => ts.isPropertyAssignment(item) &&
119
+ (ts.isIdentifier(item.name) || ts.isStringLiteral(item.name)) && item.name.text === name);
120
+ return matches.length > 0 ? matches[matches.length - 1].initializer : undefined;
121
+ };
122
+ const module = property(root, 'module');
123
+ const type = property(module, 'type');
124
+ if (!type || !ts.isStringLiteral(type) || (type.text !== 'entry' && type.text !== 'feature')) {
125
+ return false;
126
+ }
127
+ const stageEntry = property(module, 'srcEntry');
128
+ return stageEntry === undefined || (ts.isStringLiteral(stageEntry) && stageEntry.text.trim().length === 0);
129
+ } catch (_) {
130
+ return false;
131
+ }
132
+ }
133
+
134
+ /** An unresolved business class is not necessarily an Ability; trust explicit module entries. */
135
+ function isDeclaredAbilityEntry(ts: TypeScriptApi, id: string, readSource?: SourceReader): boolean {
136
+ const normalized = id.replace(/\\/g, '/');
137
+ const sourceIndex = normalized.lastIndexOf('/src/main/ets/');
138
+ if (sourceIndex < 0) return false;
139
+ const nodeRequire = require.main && typeof require.main.require === 'function'
140
+ ? require.main.require.bind(require.main) : require;
141
+ const path = nodeRequire('path');
142
+ const directory = normalized.slice(0, sourceIndex) + '/src/main';
143
+ try {
144
+ const manifest = directory + '/module.json5';
145
+ const code = readSource ? readSource(manifest) : nodeRequire('fs').readFileSync(manifest, 'utf8');
146
+ if (typeof code !== 'string') return false;
147
+ const source = ts.createSourceFile(manifest, `const __manifest = ${code}`, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
148
+ if (source.parseDiagnostics.length > 0 || source.statements.length !== 1 ||
149
+ !ts.isVariableStatement(source.statements[0])) return false;
150
+ const root = source.statements[0].declarationList.declarations[0].initializer;
151
+ const module = objectProperty(ts, root, 'module');
152
+ const entries = [objectProperty(ts, module, 'srcEntry')];
153
+ for (const key of ['abilities', 'extensionAbilities']) {
154
+ const list = objectProperty(ts, module, key);
155
+ if (list && ts.isArrayLiteralExpression(list)) {
156
+ list.elements.forEach((item: any) => entries.push(objectProperty(ts, item, 'srcEntry')));
157
+ }
158
+ }
159
+ return entries.some(entry => entry && ts.isStringLiteral(entry) && path.resolve(directory, entry.text) === path.resolve(id));
160
+ } catch (_) {
161
+ return false;
162
+ }
163
+ }
164
+
85
165
  function classifyAbilityBaseName(name: string): AbilityClassKind | null {
166
+ if (name === 'InsightIntentEntryExecutor') {
167
+ return 'intent';
168
+ }
169
+ if (name === 'InsightIntentExecutor') {
170
+ return 'legacyIntent';
171
+ }
86
172
  if (name === 'AbilityStage') {
87
173
  return 'stage';
88
174
  }
@@ -95,54 +181,218 @@ function classifyAbilityBaseName(name: string): AbilityClassKind | null {
95
181
  return null;
96
182
  }
97
183
 
98
- function collectAbilityBindings(ts: TypeScriptApi, sourceFile: any): Map<string, AbilityClassKind> {
99
- const bindings = new Map<string, AbilityClassKind>();
100
- sourceFile.statements.forEach((statement: any) => {
101
- if (!ts.isImportDeclaration(statement) || !statement.moduleSpecifier ||
102
- !ts.isStringLiteral(statement.moduleSpecifier) || !statement.importClause) {
103
- return;
104
- }
105
- const moduleName = statement.moduleSpecifier.text;
106
- if (!moduleName.startsWith('@ohos.app.ability/') && moduleName !== '@kit.AbilityKit') {
107
- return;
184
+ function bindingContains(ts: TypeScriptApi, binding: any, name: string): boolean {
185
+ if (ts.isIdentifier(binding)) {
186
+ return binding.text === name;
187
+ }
188
+ return !!binding.elements && binding.elements.some((element: any) =>
189
+ ts.isBindingElement(element) && bindingContains(ts, element.name, name));
190
+ }
191
+
192
+ function findValueDeclaration(ts: TypeScriptApi, location: any, name: string): any | null {
193
+ for (let scope = location.parent; scope; scope = scope.parent) {
194
+ if (ts.isClassDeclaration(scope) && scope.name && scope.name.text === name) {
195
+ return scope;
108
196
  }
109
- const importClause = statement.importClause;
110
- if (importClause.name) {
111
- const defaultKind = classifyAbilityBaseName(moduleName.substring(moduleName.lastIndexOf('.') + 1));
112
- if (defaultKind) {
113
- bindings.set(importClause.name.text, defaultKind);
197
+ if (ts.isFunctionLike(scope)) {
198
+ const parameter = scope.parameters.find((item: any) => bindingContains(ts, item.name, name));
199
+ if (parameter) {
200
+ return parameter;
114
201
  }
115
202
  }
116
- if (!importClause.namedBindings || !ts.isNamedImports(importClause.namedBindings)) {
117
- return;
118
- }
119
- importClause.namedBindings.elements.forEach((element: any) => {
120
- const importedName = element.propertyName ? element.propertyName.text : element.name.text;
121
- const kind = classifyAbilityBaseName(importedName);
122
- if (kind) {
123
- bindings.set(element.name.text, kind);
203
+ for (const statement of scope.statements || []) {
204
+ if (ts.isImportDeclaration(statement) && statement.importClause) {
205
+ const clause = statement.importClause;
206
+ if (clause.name && clause.name.text === name) {
207
+ return clause;
208
+ }
209
+ if (clause.namedBindings) {
210
+ if (ts.isNamespaceImport(clause.namedBindings) && clause.namedBindings.name.text === name) {
211
+ return clause.namedBindings;
212
+ }
213
+ if (ts.isNamedImports(clause.namedBindings)) {
214
+ const imported = clause.namedBindings.elements.find((item: any) => item.name.text === name);
215
+ if (imported) {
216
+ return imported;
217
+ }
218
+ }
219
+ }
124
220
  }
125
- });
126
- });
127
- return bindings;
221
+ if ((ts.isClassDeclaration(statement) || ts.isFunctionDeclaration(statement) ||
222
+ ts.isEnumDeclaration(statement) || ts.isModuleDeclaration(statement)) &&
223
+ statement.name && statement.name.text === name) {
224
+ return statement;
225
+ }
226
+ if (ts.isVariableStatement(statement)) {
227
+ const variable = statement.declarationList.declarations.find((item: any) =>
228
+ bindingContains(ts, item.name, name));
229
+ if (variable) {
230
+ return variable;
231
+ }
232
+ }
233
+ }
234
+ }
235
+ return null;
128
236
  }
129
237
 
130
- function classAbilityKind(ts: TypeScriptApi, classDeclaration: any,
131
- abilityBindings: Map<string, AbilityClassKind>): AbilityClassKind | null {
132
- if (!classDeclaration.heritageClauses) {
238
+ /** Resolve source-visible inheritance without reading through our own transform hook. */
239
+ function createAbilityClassResolver(ts: TypeScriptApi, sourceFile: any, readSource?: SourceReader):
240
+ (declaration: any) => AbilityClassInfo {
241
+ const nodeRequire = require.main && typeof require.main.require === 'function'
242
+ ? require.main.require.bind(require.main) : require;
243
+ const path = nodeRequire('path');
244
+ const sources = new Map<string, any | null>();
245
+ sources.set(path.resolve(sourceFile.fileName), sourceFile);
246
+ const classCache = new Map<any, AbilityClassInfo>();
247
+ const resolvingClasses = new Set<any>();
248
+ const resolvingExports = new Set<string>();
249
+ const unknown: AbilityBase = { kind: null };
250
+
251
+ const loadRelativeSource = (from: any, moduleName: string): any | null => {
252
+ if (!moduleName.startsWith('.')) {
253
+ return null;
254
+ }
255
+ const base = path.resolve(path.dirname(from.fileName), moduleName);
256
+ const candidates = /\.(ets|ts)$/.test(base) ? [base] :
257
+ [base + '.ets', base + '.ts', path.join(base, 'Index.ets'), path.join(base, 'index.ets'),
258
+ path.join(base, 'Index.ts'), path.join(base, 'index.ts')];
259
+ for (const fileName of candidates) {
260
+ if (!isApplicationArkTsSource(fileName)) {
261
+ continue;
262
+ }
263
+ if (!sources.has(fileName)) {
264
+ let parsed: any | null = null;
265
+ try {
266
+ const code = readSource ? readSource(fileName) : nodeRequire('fs').readFileSync(fileName, 'utf8');
267
+ if (typeof code === 'string') {
268
+ parsed = ts.createSourceFile(fileName, code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
269
+ }
270
+ } catch (_) {
271
+ // A missing or unresolved source is not evidence of an Ability base.
272
+ }
273
+ sources.set(fileName, parsed);
274
+ }
275
+ if (sources.get(fileName)) {
276
+ return sources.get(fileName);
277
+ }
278
+ }
133
279
  return null;
134
- }
135
- for (const clause of classDeclaration.heritageClauses) {
136
- if (clause.token !== ts.SyntaxKind.ExtendsKeyword || clause.types.length === 0) {
137
- continue;
280
+ };
281
+
282
+ const resolveModule = (from: any, moduleName: string, exportedName: string): AbilityBase => {
283
+ const name = exportedName === 'default' ? moduleName.slice(moduleName.lastIndexOf('.') + 1) : exportedName;
284
+ const kind = classifyAbilityBaseName(name);
285
+ if (moduleName === '@kit.AbilityKit' || moduleName.startsWith('@ohos.app.ability.') ||
286
+ (kind === 'other' && (moduleName.startsWith('@kit.') || moduleName.startsWith('@ohos.')))) {
287
+ return { kind };
138
288
  }
139
- const expression = clause.types[0].expression;
140
- if (!ts.isIdentifier(expression)) {
141
- continue;
289
+ const imported = loadRelativeSource(from, moduleName);
290
+ return imported ? resolveExport(imported, exportedName) : unknown;
291
+ };
292
+
293
+ const resolveBinding = (declaration: any, member?: string): AbilityBase => {
294
+ if (!declaration) {
295
+ return unknown;
142
296
  }
143
- return abilityBindings.get(expression.text) || classifyAbilityBaseName(expression.text);
144
- }
145
- return null;
297
+ if (ts.isClassDeclaration(declaration)) {
298
+ return { kind: resolveClass(declaration).kind, declaration };
299
+ }
300
+ let importDeclaration = declaration;
301
+ while (importDeclaration && !ts.isImportDeclaration(importDeclaration)) {
302
+ importDeclaration = importDeclaration.parent;
303
+ }
304
+ if (!importDeclaration || importDeclaration.importClause.isTypeOnly || declaration.isTypeOnly) {
305
+ return unknown;
306
+ }
307
+ let exportedName: string;
308
+ if (ts.isImportClause(declaration)) {
309
+ exportedName = 'default';
310
+ } else if (ts.isImportSpecifier(declaration)) {
311
+ exportedName = (declaration.propertyName || declaration.name).text;
312
+ } else if (ts.isNamespaceImport(declaration) && member) {
313
+ exportedName = member;
314
+ } else {
315
+ return unknown;
316
+ }
317
+ return resolveModule(importDeclaration.getSourceFile(), importDeclaration.moduleSpecifier.text, exportedName);
318
+ };
319
+
320
+ const resolveExpression = (expression: any): AbilityBase => {
321
+ if (ts.isIdentifier(expression)) {
322
+ return resolveBinding(findValueDeclaration(ts, expression, expression.text));
323
+ }
324
+ if (ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.expression)) {
325
+ return resolveBinding(findValueDeclaration(ts, expression, expression.expression.text), expression.name.text);
326
+ }
327
+ return unknown;
328
+ };
329
+
330
+ const resolveExport = (source: any, name: string): AbilityBase => {
331
+ const key = `${source.fileName}:${name}`;
332
+ if (resolvingExports.has(key)) {
333
+ return unknown;
334
+ }
335
+ resolvingExports.add(key);
336
+ try {
337
+ for (const statement of source.statements) {
338
+ if (ts.isClassDeclaration(statement)) {
339
+ const modifiers = statement.modifiers || [];
340
+ const isDefault = modifiers.some((item: any) => item.kind === ts.SyntaxKind.DefaultKeyword);
341
+ const isExported = modifiers.some((item: any) => item.kind === ts.SyntaxKind.ExportKeyword);
342
+ if ((name === 'default' && isDefault) ||
343
+ (name !== 'default' && isExported && statement.name && statement.name.text === name)) {
344
+ return resolveBinding(statement);
345
+ }
346
+ }
347
+ if (name === 'default' && ts.isExportAssignment(statement) && !statement.isExportEquals) {
348
+ return resolveExpression(statement.expression);
349
+ }
350
+ if (!ts.isExportDeclaration(statement) || statement.isTypeOnly) {
351
+ continue;
352
+ }
353
+ if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
354
+ const exported = statement.exportClause.elements.find((item: any) => item.name.text === name);
355
+ if (exported && !exported.isTypeOnly) {
356
+ const localName = (exported.propertyName || exported.name).text;
357
+ return statement.moduleSpecifier
358
+ ? resolveModule(source, statement.moduleSpecifier.text, localName)
359
+ : resolveBinding(findValueDeclaration(ts, statement, localName));
360
+ }
361
+ } else if (!statement.exportClause && statement.moduleSpecifier && name !== 'default') {
362
+ const resolved = resolveModule(source, statement.moduleSpecifier.text, name);
363
+ if (resolved.kind) {
364
+ return resolved;
365
+ }
366
+ }
367
+ }
368
+ return unknown;
369
+ } finally {
370
+ resolvingExports.delete(key);
371
+ }
372
+ };
373
+
374
+ const resolveClass = (declaration: any): AbilityClassInfo => {
375
+ const cached = classCache.get(declaration);
376
+ if (cached) {
377
+ return cached;
378
+ }
379
+ if (resolvingClasses.has(declaration)) {
380
+ return { kind: null, applicationBase: false };
381
+ }
382
+ resolvingClasses.add(declaration);
383
+ let base: AbilityBase = unknown;
384
+ for (const clause of declaration.heritageClauses || []) {
385
+ if (clause.token === ts.SyntaxKind.ExtendsKeyword && clause.types.length > 0) {
386
+ base = resolveExpression(clause.types[0].expression);
387
+ break;
388
+ }
389
+ }
390
+ const info: AbilityClassInfo = { kind: base.kind, applicationBase: !!base.declaration, base: base.declaration };
391
+ resolvingClasses.delete(declaration);
392
+ classCache.set(declaration, info);
393
+ return info;
394
+ };
395
+ return resolveClass;
146
396
  }
147
397
 
148
398
  function methodIndentation(code: string, method: any): string {
@@ -166,6 +416,50 @@ function blockIndentation(code: string, block: any): string {
166
416
  return (match ? match[0] : '') + ' ';
167
417
  }
168
418
 
419
+ function intentDecoratorString(ts: TypeScriptApi, declaration: any, field: string): string {
420
+ const decorators = ts.canHaveDecorators && ts.canHaveDecorators(declaration)
421
+ ? (ts.getDecorators(declaration) || []) : (declaration.decorators || []);
422
+ for (const decorator of decorators) {
423
+ const call = decorator.expression;
424
+ if (!ts.isCallExpression(call) || call.arguments.length === 0 ||
425
+ !ts.isObjectLiteralExpression(call.arguments[0])) {
426
+ continue;
427
+ }
428
+ for (const property of call.arguments[0].properties) {
429
+ if (ts.isPropertyAssignment(property) && property.name && property.name.text === field &&
430
+ ts.isStringLiteral(property.initializer)) {
431
+ return JSON.stringify(property.initializer.text);
432
+ }
433
+ }
434
+ }
435
+ return "''";
436
+ }
437
+
438
+ function callableMember(ts: TypeScriptApi, member: any): any | undefined {
439
+ if (ts.isMethodDeclaration(member) && member.body) return member;
440
+ if (ts.isPropertyDeclaration(member) && member.initializer &&
441
+ (ts.isArrowFunction(member.initializer) || ts.isFunctionExpression(member.initializer))) return member.initializer;
442
+ return undefined;
443
+ }
444
+
445
+ function memberName(ts: TypeScriptApi, member: any): string | undefined {
446
+ return member.name && (ts.isIdentifier(member.name) || ts.isStringLiteral(member.name)) ? member.name.text : undefined;
447
+ }
448
+
449
+ /** Insert into the actual parameter list, ignoring parentheses in return types/comments. */
450
+ function addLaunchParameters(ts: TypeScriptApi, sourceFile: any, callable: any,
451
+ added: string[], injections: ColdLaunchInjection[]): void {
452
+ const close = callable.getChildren(sourceFile).find((child: any) => child.kind === ts.SyntaxKind.CloseParenToken);
453
+ if (close) {
454
+ const separator = callable.parameters.length > 0 && !callable.parameters.hasTrailingComma ? ', ' : '';
455
+ injections.push({ position: close.getStart(sourceFile), content: separator + added.join(', ') });
456
+ } else {
457
+ // A single unparenthesized arrow parameter must become a parameter list.
458
+ injections.push({ position: callable.parameters[0].getStart(sourceFile), content: '(' });
459
+ injections.push({ position: callable.parameters.end, content: ', ' + added.join(', ') + ')' });
460
+ }
461
+ }
462
+
169
463
  function collectWindowStageParameterNames(ts: TypeScriptApi, sourceFile: any, method: any): Set<string> {
170
464
  const names = new Set<string>();
171
465
  method.parameters.forEach((parameter: any) => {
@@ -181,65 +475,252 @@ function collectWindowStageParameterNames(ts: TypeScriptApi, sourceFile: any, me
181
475
  }
182
476
 
183
477
  function createFirstFrameRetryInjections(ts: TypeScriptApi, sourceFile: any, code: string,
184
- method: any, bridgeName: string, windowStageNames: Set<string>): ColdLaunchInjection[] {
185
- const injections: ColdLaunchInjection[] = [];
478
+ method: any, bridgeName: string, windowStageNames: Set<string>, identifiers: Set<string>,
479
+ resolve: (location: any, name: string) => any | undefined): ColdLaunchInjection[] {
480
+ const candidates: any[] = [];
186
481
  visit(ts, method.body, (node: any) => {
187
- if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression)) {
188
- return;
189
- }
482
+ if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression) ||
483
+ (ts.isOptionalChain && ts.isOptionalChain(node))) return;
190
484
  const delegate = node.expression.expression;
191
485
  if (!ts.isIdentifier(delegate) || !windowStageNames.has(delegate.text) ||
192
- node.expression.name.text !== 'loadContent' || node.arguments.length === 0) {
193
- return;
194
- }
195
- const windowStageName = delegate.text;
486
+ node.expression.name.text !== 'loadContent' || node.arguments.length < 2 || node.arguments.length > 3) return;
487
+ const parameter = resolve(delegate, delegate.text);
488
+ if (!parameter || !method.parameters.includes(parameter)) return;
196
489
  const callback = node.arguments[node.arguments.length - 1];
197
- if ((!ts.isArrowFunction(callback) && !ts.isFunctionExpression(callback)) || !ts.isBlock(callback.body)) {
198
- return;
490
+ if ((!ts.isArrowFunction(callback) && !ts.isFunctionExpression(callback)) || !ts.isBlock(callback.body)) return;
491
+ candidates.push({ node, callback, parameter,
492
+ stage: selectIdentifierName(identifiers, '__ftWindowStage'),
493
+ arguments: node.arguments.slice(0, -1).map((_arg: any, index: number) =>
494
+ selectIdentifierName(identifiers, index === 0 ? '__ftContentUrl' : '__ftContentStorage')) });
495
+ });
496
+ const start = (candidate: any): number => candidate.node.getStart(sourceFile);
497
+ const contains = (outer: any, inner: any): boolean => outer !== inner &&
498
+ start(outer) <= start(inner) && outer.node.end >= inner.node.end;
499
+ const render = (from: number, to: number): string => {
500
+ const included = candidates.filter(item => start(item) >= from && item.node.end <= to);
501
+ const direct = included.filter(item => !included.some(outer => contains(outer, item)))
502
+ .sort((left, right) => start(right) - start(left));
503
+ let output = code.slice(from, to);
504
+ for (const candidate of direct) {
505
+ const rendered = renderCandidate(candidate);
506
+ output = output.slice(0, start(candidate) - from) + rendered + output.slice(candidate.node.end - from);
199
507
  }
508
+ return output;
509
+ };
510
+ const renderCandidate = (candidate: any): string => {
511
+ const { node, callback, parameter, stage } = candidate;
512
+ const bodyStart = callback.body.getStart(sourceFile) + 1;
200
513
  const indentation = blockIndentation(code, callback.body);
201
- const errorParameter = callback.parameters.length > 0 && ts.isIdentifier(callback.parameters[0].name)
514
+ const error = callback.parameters.length > 0 && ts.isIdentifier(callback.parameters[0].name)
202
515
  ? callback.parameters[0].name.text : null;
203
- const retry = `${bridgeName}.observeFirstFrame(${windowStageName});`;
204
- const content = errorParameter === null
205
- ? `\n${indentation}${retry}`
206
- : `\n${indentation}if (!${errorParameter} || !${errorParameter}.code) {\n` +
207
- `${indentation} ${retry}\n${indentation}}`;
208
- injections.push({ position: callback.body.getStart(sourceFile) + 1, content });
209
- });
210
- return injections;
516
+ const retry = `${bridgeName}.observeFirstFrame(${stage});`;
517
+ const hook = error === null ? `\n${indentation}${retry}` :
518
+ `\n${indentation}if (!${error} || !${error}.code) {\n${indentation} ${retry}\n${indentation}}`;
519
+ const callbackCode = code.slice(callback.getStart(sourceFile), bodyStart) + hook + render(bodyStart, callback.end);
520
+ const argumentNames: string[] = candidate.arguments;
521
+ const parameterType = parameter.type ? parameter.type.getText(sourceFile) : 'Object';
522
+ const declarations = [`${stage}: ${parameterType}`, ...argumentNames.map((name: string, index: number) =>
523
+ `${name}: ${index === 0 ? 'string' : 'LocalStorage'}`)];
524
+ // Evaluate the receiver and non-callback arguments at the original call site.
525
+ // Each execution captures a separate stage; callbacks cannot shadow or overwrite it.
526
+ const values = [node.expression.expression.getText(sourceFile), ...node.arguments.slice(0, -1)
527
+ .map((argument: any) => render(argument.getStart(sourceFile), argument.end))];
528
+ const statement = node.parent;
529
+ const separator = ts.isExpressionStatement(statement) && (ts.isBlock(statement.parent) ||
530
+ ts.isSourceFile(statement.parent) || ts.isCaseClause(statement.parent) || ts.isDefaultClause(statement.parent)) ? ';' : '';
531
+ return `${separator}((${declarations.join(', ')}) => ${stage}.loadContent(${argumentNames.join(', ')}, ` +
532
+ `${callbackCode}))(${values.join(', ')})`;
533
+ };
534
+ return candidates.filter(item => !candidates.some(outer => contains(outer, item)))
535
+ .map(item => ({ position: start(item), end: item.node.end, content: renderCandidate(item) }));
211
536
  }
212
537
 
213
538
  function createColdLaunchInjections(ts: TypeScriptApi, sourceFile: any, code: string,
214
- bridgeName: string, pluginVersion: string): ColdLaunchInjection[] {
539
+ bridgeName: string, pluginVersion: string, identifiers: Set<string>,
540
+ launchTypes: LaunchParameterTypes, readSource?: SourceReader, reportDiagnostic?: SourceDiagnosticReporter): ColdLaunchInjections {
215
541
  const injections: ColdLaunchInjection[] = [];
216
- const abilityBindings = collectAbilityBindings(ts, sourceFile);
542
+ let hasUIAbility = false;
543
+ let hasAbility = false;
544
+ const declaredEntry = isDeclaredAbilityEntry(ts, sourceFile.fileName, readSource);
545
+ const resolve = createLexicalResolver(ts, sourceFile);
546
+ const resolveClass = createAbilityClassResolver(ts, sourceFile, readSource);
217
547
  const pluginVersionArgument = pluginVersion.length > 0 ? `, ${JSON.stringify(pluginVersion)}` : '';
548
+ const availability = new Map<string, boolean>();
549
+ const inheritedState = (declaration: any, method: string): { covered: boolean; member?: any } => {
550
+ const seen = new Set<any>();
551
+ for (let base = resolveClass(declaration).base; base && !seen.has(base); base = resolveClass(base).base) {
552
+ seen.add(base);
553
+ const id = base.getSourceFile().fileName;
554
+ if (!availability.has(id)) availability.set(id, hasRuntimeDependency(ts, id, '@guancecloud/ft_sdk', readSource));
555
+ // An instrumented intermediate class either owns the hook or supplies a forwarding hook.
556
+ if (availability.get(id)) return { covered: true };
557
+ const member = base.members.find((item: any) => memberName(ts, item) === method &&
558
+ !(item.modifiers || []).some((modifier: any) => modifier.kind === ts.SyntaxKind.StaticKeyword));
559
+ if (member) return { covered: false, member };
560
+ }
561
+ return { covered: false };
562
+ };
563
+ const wrapInherited = (declaration: any, name: string, hook: string, launch: boolean, inherited: any): void => {
564
+ const original = selectIdentifierName(identifiers, '__ftInheritedCall');
565
+ const want = selectIdentifierName(identifiers, '__ftInheritedWant');
566
+ const param = selectIdentifierName(identifiers, '__ftInheritedLaunch');
567
+ if (launch) launchTypes.needed = true;
568
+ const baseType = declaration.heritageClauses.find((item: any) => item.token === ts.SyntaxKind.ExtendsKeyword)
569
+ .types[0].getText(sourceFile);
570
+ const callable = callableMember(ts, inherited);
571
+ const signature = callable || (inherited.type && ts.isFunctionTypeNode(inherited.type) ? inherited.type : undefined);
572
+ const count = signature && !signature.parameters.some((parameter: any) => parameter.dotDotDotToken)
573
+ ? signature.parameters.length : 2;
574
+ const parameters = launch ? `${want}?: ${launchTypes.want}, ${param}?: ${launchTypes.abilityConstant}.LaunchParam` : '';
575
+ const passed = launch ? [`${want} as ${launchTypes.want}`, `${param} as ${launchTypes.abilityConstant}.LaunchParam`]
576
+ .slice(0, count).join(', ') : '';
577
+ const hookText = hook.replace(/\$want/g, want).replace(/\$launch/g, param);
578
+ // Store the original callable on the same instance. Calling this slot keeps
579
+ // its receiver and concrete return type, including inherited function fields,
580
+ // without bind's dynamic return type or unsupported generic inference.
581
+ injections.push({ position: declaration.end - 1,
582
+ content: `\n private ${original} = (this as Object as ${baseType}).${name};\n` +
583
+ ` ${name} = (${parameters}) => { ${hookText} return this.${original}(${passed}); };\n` });
584
+ };
585
+
218
586
  visit(ts, sourceFile, (node: any) => {
219
587
  if (!ts.isClassDeclaration(node)) {
220
588
  return;
221
589
  }
222
- const kind = classAbilityKind(ts, node, abilityBindings);
590
+ const classification = resolveClass(node);
591
+ const kind = classification.kind;
223
592
  if (!kind) {
593
+ if (declaredEntry && (node.modifiers || []).some((modifier: any) => modifier.kind === ts.SyntaxKind.DefaultKeyword)) {
594
+ reportSourceDiagnostic(sourceFile, node.getStart(sourceFile), 'unresolved-ability-base',
595
+ 'This module entry has no resolvable platform Ability/AbilityStage/InsightIntent base; automatic lifecycle instrumentation was skipped.',
596
+ reportDiagnostic);
597
+ }
224
598
  return;
225
599
  }
600
+ hasAbility = true;
601
+ if (kind === 'ui') {
602
+ hasUIAbility = true;
603
+ }
226
604
  let hasOnCreate = false;
227
- node.members.forEach((member: any) => {
228
- if (!ts.isMethodDeclaration(member) || !member.body || !member.name || !ts.isIdentifier(member.name)) {
605
+ let hasOnForeground = false;
606
+ let hasOnBackground = false;
607
+ let hasOnNewWant = false;
608
+ node.members.forEach((declaration: any) => {
609
+ if ((declaration.modifiers || []).some((modifier: any) => modifier.kind === ts.SyntaxKind.StaticKeyword)) return;
610
+ const name = memberName(ts, declaration);
611
+ if (name === 'onCreate') hasOnCreate = true;
612
+ if (name === 'onForeground') hasOnForeground = true;
613
+ if (name === 'onBackground') hasOnBackground = true;
614
+ if (name === 'onNewWant') hasOnNewWant = true;
615
+ const member = callableMember(ts, declaration);
616
+ if (!name) return;
617
+ const expected = kind === 'intent' ? name === 'onExecute' : kind === 'legacyIntent'
618
+ ? ['onExecuteInUIAbilityForegroundMode', 'onExecuteInUIAbilityBackgroundMode'].includes(name)
619
+ : name === 'onCreate' || (kind === 'ui' &&
620
+ ['onForeground', 'onBackground', 'onNewWant', 'onWindowStageCreate'].includes(name));
621
+ if (!member) {
622
+ if (expected && !node.members.some((other: any) => memberName(ts, other) === name && callableMember(ts, other))) {
623
+ reportSourceDiagnostic(sourceFile, declaration.getStart(sourceFile), 'unsupported-lifecycle',
624
+ `${name} is not a method or function property with an instrumentable body; its automatic hook was skipped.`, reportDiagnostic);
625
+ }
626
+ return;
627
+ }
628
+ if (kind === 'ui' && name === 'onCreate' && member.parameters.slice(0, 2).some((parameter: any) =>
629
+ !ts.isIdentifier(parameter.name) || parameter.dotDotDotToken)) {
630
+ reportSourceDiagnostic(sourceFile, declaration.getStart(sourceFile), 'unsupported-lifecycle-parameters',
631
+ 'onCreate uses destructured or rest parameters; automatic launch-reason capture was skipped.', reportDiagnostic);
632
+ return;
633
+ }
634
+ if (kind === 'ui' && name === 'onWindowStageCreate' && (member.parameters.length === 0 ||
635
+ !ts.isIdentifier(member.parameters[0].name) || member.parameters[0].dotDotDotToken)) {
636
+ reportSourceDiagnostic(sourceFile, declaration.getStart(sourceFile), 'unsupported-lifecycle-parameters',
637
+ 'onWindowStageCreate requires a named WindowStage parameter for automatic first-frame observation.', reportDiagnostic);
638
+ return;
639
+ }
640
+ const indentation = methodIndentation(code, declaration);
641
+ const block = ts.isBlock(member.body);
642
+ const position = member.body.getStart(sourceFile) + (block ? 1 : 0);
643
+ // Expression-bodied lifecycle properties retain their expression result.
644
+ if (!block) {
645
+ const recognized = kind === 'intent' ? name === 'onExecute' : kind === 'legacyIntent'
646
+ ? ['onExecuteInUIAbilityForegroundMode', 'onExecuteInUIAbilityBackgroundMode'].includes(name)
647
+ : name === 'onCreate' || (kind === 'ui' &&
648
+ (['onForeground', 'onBackground', 'onNewWant'].includes(name) ||
649
+ (name === 'onWindowStageCreate' && member.parameters.length > 0 && ts.isIdentifier(member.parameters[0].name))));
650
+ if (recognized) {
651
+ injections.push({ position, content: '{' });
652
+ injections.push({ position: member.body.end, content: '; }' });
653
+ }
654
+ }
655
+ const finish = (): void => {
656
+ if (!block) injections.push({ position, content: '\n' + indentation + 'return ' });
657
+ };
658
+ if (kind === 'intent' || kind === 'legacyIntent') {
659
+ let mode: string | null = null;
660
+ if (kind === 'intent' && name === 'onExecute') {
661
+ mode = 'this.executeMode';
662
+ } else if (kind === 'legacyIntent' && name === 'onExecuteInUIAbilityForegroundMode') {
663
+ mode = '0';
664
+ } else if (kind === 'legacyIntent' && name === 'onExecuteInUIAbilityBackgroundMode') {
665
+ mode = '1';
666
+ }
667
+ if (mode !== null) {
668
+ const intentName = kind === 'intent' ? intentDecoratorString(ts, node, 'intentName') :
669
+ (member.parameters.length > 0 && ts.isIdentifier(member.parameters[0].name)
670
+ ? member.parameters[0].name.text : "''");
671
+ const abilityName = kind === 'intent' ? intentDecoratorString(ts, node, 'abilityName') : "''";
672
+ injections.push({ position, content: `\n${indentation}${bridgeName}.recordInsightIntentExecute(` +
673
+ `${mode}, ${intentName}, this.context, ${abilityName});` });
674
+ }
675
+ if (mode !== null) finish();
229
676
  return;
230
677
  }
231
- const indentation = methodIndentation(code, member);
232
- const position = member.body.getStart(sourceFile) + 1;
233
- if (member.name.text === 'onCreate') {
678
+ if (name === 'onCreate') {
234
679
  hasOnCreate = true;
235
- let content = `\n${indentation}${bridgeName}.recordApplicationInit(this.context${pluginVersionArgument});`;
680
+ let content = kind === 'ui' ? '' :
681
+ `\n${indentation}${bridgeName}.recordApplicationInit(this.context${pluginVersionArgument});`;
236
682
  if (kind === 'ui') {
237
- content += `\n${indentation}${bridgeName}.recordFirstUIAbilityCreate();`;
683
+ const parameters = member.parameters;
684
+ let wantName = parameters.length > 0 && ts.isIdentifier(parameters[0].name)
685
+ ? parameters[0].name.text : 'undefined';
686
+ let launchParamName = parameters.length > 1 && ts.isIdentifier(parameters[1].name)
687
+ ? parameters[1].name.text : 'undefined';
688
+ // Even an onCreate() that ignores both parameters must pass the
689
+ // actual system reason to the SDK. Add typed optional parameters
690
+ // to the in-memory source without changing the application's file.
691
+ if (parameters.length < 2) {
692
+ launchTypes.needed = true;
693
+ const added: string[] = [];
694
+ if (parameters.length === 0) {
695
+ wantName = selectIdentifierName(identifiers, '__ftLaunchWant');
696
+ added.push(`${wantName}?: ${launchTypes.want}`);
697
+ }
698
+ launchParamName = selectIdentifierName(identifiers, '__ftLaunchParam');
699
+ added.push(`${launchParamName}?: ${launchTypes.abilityConstant}.LaunchParam`);
700
+ addLaunchParameters(ts, sourceFile, member, added, injections);
701
+ }
702
+ content += `\n${indentation}${bridgeName}.recordFirstUIAbilityCreate(${launchParamName}, ${wantName}, this);`;
238
703
  }
239
704
  injections.push({ position, content });
705
+ finish();
706
+ return;
707
+ }
708
+ if (kind === 'ui' && (name === 'onForeground' || name === 'onBackground' ||
709
+ name === 'onNewWant')) {
710
+ if (name === 'onForeground') {
711
+ hasOnForeground = true;
712
+ } else if (name === 'onBackground') {
713
+ hasOnBackground = true;
714
+ } else {
715
+ hasOnNewWant = true;
716
+ }
717
+ const hook = name === 'onForeground' ? 'recordUIAbilityForeground' :
718
+ (name === 'onBackground' ? 'recordUIAbilityBackground' : 'recordUIAbilityNewWant');
719
+ injections.push({ position, content: `\n${indentation}${bridgeName}.${hook}(this);` });
720
+ finish();
240
721
  return;
241
722
  }
242
- if (kind !== 'ui' || member.name.text !== 'onWindowStageCreate' || member.parameters.length === 0) {
723
+ if (kind !== 'ui' || name !== 'onWindowStageCreate' || member.parameters.length === 0) {
243
724
  return;
244
725
  }
245
726
  const windowStageParameter = member.parameters[0];
@@ -249,64 +730,130 @@ function createColdLaunchInjections(ts: TypeScriptApi, sourceFile: any, code: st
249
730
  const windowStageName = windowStageParameter.name.text;
250
731
  injections.push({
251
732
  position,
252
- content: `\n${indentation}${bridgeName}.observeFirstFrame(${windowStageName});`
733
+ content: `\n${indentation}if (${windowStageName}) { ${bridgeName}.observeFirstFrame(${windowStageName}); }`
253
734
  });
735
+ finish();
254
736
  });
255
737
  if (kind === 'ui') {
256
- node.members.forEach((member: any) => {
257
- if (!ts.isMethodDeclaration(member) || !member.body) {
258
- return;
259
- }
738
+ node.members.forEach((declaration: any) => {
739
+ if ((declaration.modifiers || []).some((modifier: any) => modifier.kind === ts.SyntaxKind.StaticKeyword)) return;
740
+ const member = callableMember(ts, declaration);
741
+ if (!member) return;
260
742
  const windowStageNames = collectWindowStageParameterNames(ts, sourceFile, member);
261
743
  if (windowStageNames.size > 0) {
262
744
  injections.push(...createFirstFrameRetryInjections(
263
- ts, sourceFile, code, member, bridgeName, windowStageNames));
745
+ ts, sourceFile, code, member, bridgeName, windowStageNames, identifiers, resolve));
264
746
  }
265
747
  });
266
748
  }
267
749
  if (kind === 'stage' && !hasOnCreate) {
268
- const indentation = classIndentation(code, node);
269
- injections.push({
270
- position: node.end - 1,
271
- content: `\n${indentation} onCreate(): void {\n` +
272
- `${indentation} ${bridgeName}.recordApplicationInit(this.context${pluginVersionArgument});\n` +
273
- `${indentation} }\n`
274
- });
750
+ const inherited = inheritedState(node, 'onCreate');
751
+ if (!inherited.covered) {
752
+ const hook = `${bridgeName}.recordApplicationInit(this.context${pluginVersionArgument});`;
753
+ if (inherited.member) wrapInherited(node, 'onCreate', hook, false, inherited.member);
754
+ else injections.push({ position: node.end - 1,
755
+ content: `\n onCreate(): void { ${hook} }\n` });
756
+ }
757
+ }
758
+ if (kind === 'ui' && !hasOnCreate) {
759
+ const inherited = inheritedState(node, 'onCreate');
760
+ if (!inherited.covered) {
761
+ launchTypes.needed = true;
762
+ if (inherited.member) {
763
+ wrapInherited(node, 'onCreate', `${bridgeName}.recordFirstUIAbilityCreate($launch, $want, this);`, true, inherited.member);
764
+ } else {
765
+ const wantName = selectIdentifierName(identifiers, '__ftLaunchWant');
766
+ const paramName = selectIdentifierName(identifiers, '__ftLaunchParam');
767
+ injections.push({ position: node.end - 1, content: `\n onCreate(${wantName}: ${launchTypes.want}, ` +
768
+ `${paramName}: ${launchTypes.abilityConstant}.LaunchParam): void {\n` +
769
+ ` ${bridgeName}.recordFirstUIAbilityCreate(${paramName}, ${wantName}, this);\n }\n` });
770
+ }
771
+ }
772
+ }
773
+ if (kind === 'ui') {
774
+ for (const [method, hook, exists] of [
775
+ ['onForeground', 'recordUIAbilityForeground', hasOnForeground],
776
+ ['onBackground', 'recordUIAbilityBackground', hasOnBackground],
777
+ ['onNewWant', 'recordUIAbilityNewWant', hasOnNewWant]
778
+ ]) {
779
+ if (exists) continue;
780
+ const inherited = inheritedState(node, method as string);
781
+ if (inherited.covered) continue;
782
+ const call = `${bridgeName}.${hook}(this);`;
783
+ if (inherited.member) wrapInherited(node, method as string, call, method === 'onNewWant', inherited.member);
784
+ else injections.push({ position: node.end - 1, content: `\n ${method}(): void { ${call} }\n` });
785
+ }
275
786
  }
276
787
  });
277
- return injections;
788
+ return { injections, hasUIAbility, hasAbility };
278
789
  }
279
790
 
280
791
  function applyColdLaunchInjections(code: string, injections: ColdLaunchInjection[]): string {
281
792
  const contentsByPosition = new Map<number, string[]>();
793
+ const ends = new Map<number, number>();
282
794
  injections.forEach((injection: ColdLaunchInjection) => {
283
795
  const contents = contentsByPosition.get(injection.position) || [];
284
796
  contents.push(injection.content);
285
797
  contentsByPosition.set(injection.position, contents);
798
+ ends.set(injection.position, Math.max(ends.get(injection.position) || injection.position, injection.end || injection.position));
286
799
  });
287
800
  const positions = Array.from(contentsByPosition.keys()).sort((left: number, right: number) => right - left);
288
801
  let transformedCode = code;
289
802
  positions.forEach((position: number) => {
290
803
  const content = (contentsByPosition.get(position) || []).join('');
291
- transformedCode = transformedCode.slice(0, position) + content + transformedCode.slice(position);
804
+ transformedCode = transformedCode.slice(0, position) + content + transformedCode.slice(ends.get(position));
292
805
  });
293
806
  return transformedCode;
294
807
  }
295
808
 
296
- export function transformColdLaunchSource(ts: TypeScriptApi, id: string, code: string): string {
297
- if (!isApplicationArkTsSource(id) || code.includes(COLD_LAUNCH_TRANSFORM_MARKER)) {
809
+ export function transformColdLaunchSource(ts: TypeScriptApi, id: string, code: string,
810
+ readSource?: SourceReader, reportDiagnostic?: SourceDiagnosticReporter): string {
811
+ if (!isApplicationArkTsSource(id) || code.includes(COLD_LAUNCH_TRANSFORM_MARKER) ||
812
+ !hasRuntimeDependency(ts, id, '@guancecloud/ft_sdk', readSource)) {
298
813
  return code;
299
814
  }
300
815
  const sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
301
- const bridgeName = selectBridgeName(collectIdentifierNames(ts, sourceFile));
816
+ const identifiers = collectIdentifierNames(ts, sourceFile);
817
+ const bridgeName = selectIdentifierName(identifiers, '__ftColdLaunchBridge');
818
+ const launchTypes: LaunchParameterTypes = {
819
+ want: selectIdentifierName(identifiers, '__ftLaunchWantType'),
820
+ abilityConstant: selectIdentifierName(identifiers, '__ftLaunchAbilityConstant'),
821
+ needed: false
822
+ };
302
823
  const version = getPluginVersion();
303
- const injections = createColdLaunchInjections(ts, sourceFile, code, bridgeName, version);
304
- if (injections.length === 0) {
824
+ const diagnostics: SourceTransformDiagnostic[] = [];
825
+ const result = createColdLaunchInjections(ts, sourceFile, code, bridgeName, version, identifiers,
826
+ launchTypes, readSource, diagnostic => diagnostics.push(diagnostic));
827
+ if (result.hasAbility || diagnostics.length > 0) {
828
+ if (reportSourceParseError(ts, parseApplicationSource(ts, id, code), reportDiagnostic)) return code;
829
+ diagnostics.forEach(diagnostic => reportDiagnostic?.(diagnostic));
830
+ }
831
+ const injections = result.injections;
832
+ const moduleFallback = result.hasUIAbility && needsUIAbilityModuleFallback(ts, id, readSource);
833
+ if (injections.length === 0 && !moduleFallback) {
305
834
  return code;
306
835
  }
307
- const insertionOffset = findImportInsertionOffset(ts, sourceFile);
836
+ // Keep generated imports separate from initialization calls: ArkTS requires
837
+ // imports before other statements, while the no-Stage fallback must still
838
+ // run before executable application code.
839
+ const insertionOffset = moduleFallback
840
+ ? (code.startsWith('#!') ? code.indexOf('\n') + 1 : 0)
841
+ : findImportInsertionOffset(ts, sourceFile);
842
+ const firstApplicationStatement = sourceFile.statements.find((statement: any) => !ts.isImportDeclaration(statement));
843
+ const initializationOffset = moduleFallback && firstApplicationStatement
844
+ ? firstApplicationStatement.getStart(sourceFile) : insertionOffset;
845
+ const installBridgeName = selectIdentifierName(identifiers, '__ftColdLaunchInstallBridge');
846
+ const sdkName = selectIdentifierName(identifiers, '__ftColdLaunchSDK');
308
847
  const bridgeImport = `\n${COLD_LAUNCH_TRANSFORM_MARKER}\n` +
309
- `import { FTColdLaunchBridge as ${bridgeName} } from '${COLD_LAUNCH_BRIDGE_IMPORT}';\n`;
310
- const transformedCode = applyColdLaunchInjections(code, injections);
311
- return transformedCode.slice(0, insertionOffset) + bridgeImport + transformedCode.slice(insertionOffset);
848
+ `import { FTColdLaunchBridge as ${bridgeName}, FTConsoleLogBridge as ${installBridgeName}` +
849
+ (version.length > 0 ? `, FTSDK as ${sdkName}` : '') + ` } from '${COLD_LAUNCH_BRIDGE_IMPORT}';\n` +
850
+ (launchTypes.needed ? `import { Want as ${launchTypes.want}, AbilityConstant as ${launchTypes.abilityConstant} } ` +
851
+ `from '@kit.AbilityKit';\n` : '');
852
+ const initialization = `\n${installBridgeName}.markCompilerPluginInstalled();\n` +
853
+ (version.length > 0 ? `${sdkName}.registerHvigorPluginVersion(${JSON.stringify(version)});\n` : '') +
854
+ (moduleFallback ? '// Application-init fallback: UIAbility module evaluation without an AbilityStage entry.\n' +
855
+ `${bridgeName}.recordApplicationInit(undefined, ${JSON.stringify(version)});\n` : '');
856
+ injections.push({ position: insertionOffset, content: bridgeImport });
857
+ injections.push({ position: initializationOffset, content: initialization });
858
+ return applyColdLaunchInjections(code, injections);
312
859
  }