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