diff --git a/generators/bootstrap/support/java-lint-worker.ts b/generators/bootstrap/support/java-lint-worker.ts index 3557e06b489a..7ab665ca1f7a 100644 --- a/generators/bootstrap/support/java-lint-worker.ts +++ b/generators/bootstrap/support/java-lint-worker.ts @@ -1,4 +1,4 @@ -import { removeUnusedImports } from 'java-lint'; +import { removeUnusedImports } from '../../java/support/index.ts'; export default ({ fileContents }: { fileContents: string }): { result: string } | { error: string } => { try { diff --git a/generators/java/support/index.ts b/generators/java/support/index.ts index 93d29fd427f7..4ff242be018a 100644 --- a/generators/java/support/index.ts +++ b/generators/java/support/index.ts @@ -30,4 +30,5 @@ export { default as packageInfoTransform } from './package-info-transform.ts'; export * from './package-info-transform.ts'; export * from './prepare-entity.ts'; export * from './reserved-keywords.ts'; +export { removeUnusedImports } from './unused-imports.ts'; export * from './util.ts'; diff --git a/generators/java/support/unused-imports.spec.ts b/generators/java/support/unused-imports.spec.ts new file mode 100644 index 000000000000..3ce130b08506 --- /dev/null +++ b/generators/java/support/unused-imports.spec.ts @@ -0,0 +1,140 @@ +import { describe, expect, it } from 'esmocha'; + +import { parse } from 'java-parser'; + +import { collectGlobalIdentifiersNodes, removeUnusedImports } from './unused-imports.ts'; + +const source = `package my.java.project; + +import java.util.*; + +import project.Used1; +import project.Unused1; +import project.Used2; +import project.Unused2; + +public class HelloWorldExample { + public static void main(Used1 args[]) { + List arguments = java.util.Arrays.asList(args); + System.out.println("Arguments:"); + System.out.println(arguments); + } +} +`; + +describe('java-lint', () => { + it('collectGlobalIdentifiersNodes', () => { + expect(collectGlobalIdentifiersNodes(parse(source))).toMatchInlineSnapshot(` + [ + "HelloWorldExample", + "main", + "args", + "Used1", + "arguments", + "List", + "Used2", + "System", + "out", + "println", + "java", + "util", + "Arrays", + "asList", + ] + `); + }); + describe('removeUnusedImports', () => { + it('should remove unused imports', () => { + expect(removeUnusedImports(source)).toMatchInlineSnapshot(` + "package my.java.project; + + import java.util.*; + + import project.Used1; + import project.Used2; + + public class HelloWorldExample { + public static void main(Used1 args[]) { + List arguments = java.util.Arrays.asList(args); + System.out.println("Arguments:"); + System.out.println(arguments); + } + } + " + `); + }); + + it('should remove same package imports', () => { + expect( + removeUnusedImports(`package my.java.project; + +import my.java.project.Used1; +import my.java.project.Used2; + +public class HelloWorldExample { + public static void main(Used1 args[]) { + List arguments = java.util.Arrays.asList(args); + System.out.println("Arguments:"); + System.out.println(arguments); + } +} +`), + ).toMatchInlineSnapshot(` + "package my.java.project; + + + public class HelloWorldExample { + public static void main(Used1 args[]) { + List arguments = java.util.Arrays.asList(args); + System.out.println("Arguments:"); + System.out.println(arguments); + } + } + " + `); + }); + + it('should not fail with emptyStatement', () => { + expect( + removeUnusedImports(`package my.java.project; + +import my.java.project.Used1;; + +public class HelloWorldExample {} +`), + ).toMatchInlineSnapshot(` + "package my.java.project; + ; + + public class HelloWorldExample {} + " + `); + }); + + it('should not remove import static when', () => { + expect( + removeUnusedImports(`package my.java.project; + +import static org.mockito.Mockito.when; + +public class HelloWorldExample { + public static void main() { + when(); + } +} +`), + ).toMatchInlineSnapshot(` + "package my.java.project; + + import static org.mockito.Mockito.when; + + public class HelloWorldExample { + public static void main() { + when(); + } + } + " + `); + }); + }); +}); diff --git a/generators/java/support/unused-imports.ts b/generators/java/support/unused-imports.ts new file mode 100644 index 000000000000..7c4e4bcaf4e5 --- /dev/null +++ b/generators/java/support/unused-imports.ts @@ -0,0 +1,79 @@ +import { type CstElement, type CstNode, parse } from 'java-parser'; + +const skippedTypes = ['packageDeclaration', 'importDeclaration']; + +/** + * Lazy implementation of used global identifiers collector. + * @param cstNode + * @returns + */ +export const collectGlobalIdentifiersNodes = (cstNode: CstNode): string[] => { + const nodes = [cstNode]; + const identifiers: Set = new Set(); + + for (let node; (node = nodes.shift()); ) { + for (const identifier of Object.keys(node.children)) { + node.children[identifier] + .filter((element: CstElement) => !(element as any).name || !skippedTypes.includes((element as any).name)) + .forEach((element: CstElement) => { + if ('image' in element) { + if (element.image) { + const tokenTypes = [element.tokenType]; + if (Array.isArray(element.tokenType.LONGER_ALT)) { + tokenTypes.push(...element.tokenType.LONGER_ALT); + } else if (element.tokenType.LONGER_ALT) { + tokenTypes.push(element.tokenType.LONGER_ALT); + } + if (tokenTypes.some(({ name, isParent }) => name === 'Identifier' && isParent)) { + const categories = tokenTypes.map(({ CATEGORIES }) => CATEGORIES).flat(); + if (!categories.some(cat => cat?.name === 'Keyword')) { + identifiers.add(element.image); + } + } + } + } else { + nodes.push(element); + } + }); + } + } + return [...identifiers]; +}; + +export const removeUnusedImports = (content: string) => { + const cstNode = parse(content); + const importDeclarationNodes: any[] = (cstNode.children.ordinaryCompilationUnit[0] as any).children.importDeclaration; + if (!importDeclarationNodes) { + return content; + } + const filePackage = (cstNode.children.ordinaryCompilationUnit[0] as any).children.packageDeclaration[0].children.Identifier.map( + (identifier: any) => identifier.image, + ).join('.'); + const identifiers = collectGlobalIdentifiersNodes(cstNode); + const unusedImportNodes: any[] = importDeclarationNodes + .filter(importDec => !importDec.children.Star && !importDec.children.emptyStatement) + .map(imp => { + const packageOrTypeName = imp.children.packageOrTypeName[0]; + return [packageOrTypeName.children.Identifier[packageOrTypeName.children.Identifier.length - 1].image, imp]; + }) + .filter( + ([identifier, importDec]) => + !identifiers.includes(identifier) || + importDec.children.packageOrTypeName[0].children.Identifier.map((el: any) => el.image) + .slice(0, -1) + .join('.') === filePackage, + ) + .map(([_identifier, impNode]) => impNode); + + // Reverse + unusedImportNodes.sort((a, b) => b.location.startOffset - a.location.startOffset); + + for (const unusedImport of unusedImportNodes) { + let { startOffset } = unusedImport.location; + if (content.charAt(startOffset - 1) === '\n') { + startOffset--; + } + content = `${content.slice(0, startOffset)}${content.slice(unusedImport.location.endOffset + 1)}`; + } + return content; +}; diff --git a/package-lock.json b/package-lock.json index e6b03c0130b6..2a34d1e5e26a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "fast-xml-parser": "5.5.6", "globals": "17.4.0", "isbinaryfile": "5.0.0", - "java-lint": "0.3.0", + "java-parser": "3.0.1", "latest-version": "9.0.0", "lodash-es": "4.17.23", "mem-fs": "4.1.4", @@ -6918,26 +6918,10 @@ "node": ">=10" } }, - "node_modules/java-lint": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/java-lint/-/java-lint-0.3.0.tgz", - "integrity": "sha512-//tpg1P1J9ubTLJWxMrz7//GPfnAueB/UYb8zIsL5uibuKN2H+puWjfqa0KCUO+bKmK65Tcg1bZvFmn979atcw==", - "license": "MIT", - "dependencies": { - "java-parser": "~2.3.0" - }, - "engines": { - "node": "^18.13.0 || >= 20.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/marcelo-boveto-shima" - } - }, "node_modules/java-parser": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/java-parser/-/java-parser-2.3.4.tgz", - "integrity": "sha512-5fnUDL2AUYX7gs6YlfyVmvq+bFL+HlSBE+MdPdUsfaE898KObdu9aafJCKTJIzXi2IF+P/RiCA5kvGc0msxhYA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/java-parser/-/java-parser-3.0.1.tgz", + "integrity": "sha512-sDIR7u9b7O2JViNUxiZRhnRz7URII/eE7g2B+BmGxDeS6Ex3OYAcCyz5oh0H4LQ+hL/BS8OJTz8apMy9xtGmrQ==", "license": "Apache-2.0", "dependencies": { "chevrotain": "11.0.3", @@ -9216,88 +9200,6 @@ "prettier": "^3.0.0" } }, - "node_modules/prettier-plugin-java/node_modules/@chevrotain/cst-dts-gen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", - "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "11.0.3", - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/prettier-plugin-java/node_modules/@chevrotain/gast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", - "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/prettier-plugin-java/node_modules/@chevrotain/regexp-to-ast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", - "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", - "license": "Apache-2.0" - }, - "node_modules/prettier-plugin-java/node_modules/@chevrotain/types": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", - "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", - "license": "Apache-2.0" - }, - "node_modules/prettier-plugin-java/node_modules/@chevrotain/utils": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", - "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", - "license": "Apache-2.0" - }, - "node_modules/prettier-plugin-java/node_modules/chevrotain": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", - "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/cst-dts-gen": "11.0.3", - "@chevrotain/gast": "11.0.3", - "@chevrotain/regexp-to-ast": "11.0.3", - "@chevrotain/types": "11.0.3", - "@chevrotain/utils": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/prettier-plugin-java/node_modules/chevrotain-allstar": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", - "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", - "license": "MIT", - "dependencies": { - "lodash-es": "^4.17.21" - }, - "peerDependencies": { - "chevrotain": "^11.0.0" - } - }, - "node_modules/prettier-plugin-java/node_modules/java-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/java-parser/-/java-parser-3.0.1.tgz", - "integrity": "sha512-sDIR7u9b7O2JViNUxiZRhnRz7URII/eE7g2B+BmGxDeS6Ex3OYAcCyz5oh0H4LQ+hL/BS8OJTz8apMy9xtGmrQ==", - "license": "Apache-2.0", - "dependencies": { - "chevrotain": "11.0.3", - "chevrotain-allstar": "0.3.1", - "lodash": "4.17.21" - } - }, - "node_modules/prettier-plugin-java/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/prettier-plugin-packagejson": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-3.0.2.tgz", diff --git a/package.json b/package.json index 65052a1f4929..d25d85b26fdd 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,7 @@ "fast-xml-parser": "5.5.6", "globals": "17.4.0", "isbinaryfile": "5.0.0", - "java-lint": "0.3.0", + "java-parser": "3.0.1", "latest-version": "9.0.0", "lodash-es": "4.17.23", "mem-fs": "4.1.4",