1import { CodeBlock, insertContentsAtOffset } from '../utils/commonCodeMod';
2import { findMatchingBracketPosition } from '../utils/matchBrackets';
3
4/**
5 * Find java or kotlin new class instance code block
6 *
7 * @param contents source contents
8 * @param classDeclaration class declaration or just a class name
9 * @param language 'java' | 'kt'
10 * @returns `CodeBlock` for start/end offset and code block contents
11 */
12export function findNewInstanceCodeBlock(
13  contents: string,
14  classDeclaration: string,
15  language: 'java' | 'kt'
16): CodeBlock | null {
17  const isJava = language === 'java';
18  let start = isJava
19    ? contents.indexOf(` new ${classDeclaration}(`)
20    : contents.search(new RegExp(` (object\\s*:\\s*)?${classDeclaration}\\(`));
21  if (start < 0) {
22    return null;
23  }
24  // `+ 1` for the prefix space
25  start += 1;
26  let end = findMatchingBracketPosition(contents, '(', start);
27
28  // For anonymous class, should search further to the {} block.
29  // ```java
30  // new Foo() {
31  //   @Override
32  //   protected void interfaceMethod {}
33  // };
34  // ```
35  //
36  // ```kotlin
37  // object : Foo() {
38  //   override fun interfaceMethod {}
39  // }
40  // ```
41  const nextBrace = contents.indexOf('{', end + 1);
42  const isAnonymousClass =
43    nextBrace >= end && !!contents.substring(end + 1, nextBrace).match(/^\s*$/);
44  if (isAnonymousClass) {
45    end = findMatchingBracketPosition(contents, '{', end);
46  }
47
48  return {
49    start,
50    end,
51    code: contents.substring(start, end + 1),
52  };
53}
54
55/**
56 * Append contents to the end of code declaration block, support class or method declarations.
57 *
58 * @param srcContents source contents
59 * @param declaration class declaration or method declaration
60 * @param insertion code to append
61 * @returns updated contents
62 */
63export function appendContentsInsideDeclarationBlock(
64  srcContents: string,
65  declaration: string,
66  insertion: string
67): string {
68  const start = srcContents.search(new RegExp(`\\s*${declaration}.*?[\\(\\{]`));
69  if (start < 0) {
70    throw new Error(`Unable to find code block - declaration[${declaration}]`);
71  }
72  const end = findMatchingBracketPosition(srcContents, '{', start);
73  return insertContentsAtOffset(srcContents, insertion, end);
74}
75
76export function addImports(source: string, imports: string[], isJava: boolean): string {
77  const lines = source.split('\n');
78  const lineIndexWithPackageDeclaration = lines.findIndex((line) => line.match(/^package .*;?$/));
79  for (const javaImport of imports) {
80    if (!source.includes(javaImport)) {
81      const importStatement = `import ${javaImport}${isJava ? ';' : ''}`;
82      lines.splice(lineIndexWithPackageDeclaration + 1, 0, importStatement);
83    }
84  }
85  return lines.join('\n');
86}
87