diff --git a/package-lock.json b/package-lock.json index 42bf798..4b9378d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@genese/complexity", - "version": "1.1.15", + "version": "1.1.21", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@genese/complexity", - "version": "1.1.15", + "version": "1.1.21", "license": "MIT", "dependencies": { "@genese/core": "^1.0.0-alpha.1", @@ -24,6 +24,10 @@ }, "bin": { "complexity": "dist/src/index.js" + }, + "devDependencies": { + "@types/react": "^17.0.39", + "react": "^17.0.2" } }, "node_modules/@dsherret/to-absolute-glob": { @@ -116,6 +120,29 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.41.tgz", "integrity": "sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g==" }, + "node_modules/@types/prop-types": { + "version": "15.7.4", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", + "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", + "dev": true + }, + "node_modules/@types/react": { + "version": "17.0.39", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.39.tgz", + "integrity": "sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, "node_modules/ansi-escapes": { "version": "4.3.1", "resolved": "http://localhost:4873/ansi-escapes/-/ansi-escapes-4.3.1.tgz", @@ -291,6 +318,12 @@ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, + "node_modules/csstype": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", + "dev": true + }, "node_modules/csv-writer": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/csv-writer/-/csv-writer-1.6.0.tgz", @@ -506,6 +539,12 @@ "lodash": "4.17.15" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "http://localhost:4873/jsonfile/-/jsonfile-6.1.0.tgz", @@ -539,6 +578,18 @@ "node": ">=10" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -613,6 +664,15 @@ "resolved": "http://localhost:4873/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/onetime": { "version": "5.1.2", "resolved": "http://localhost:4873/onetime/-/onetime-5.1.2.tgz", @@ -650,6 +710,19 @@ "node": ">=8.6" } }, + "node_modules/react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/regexp-to-ast": { "version": "0.4.0", "resolved": "http://localhost:4873/regexp-to-ast/-/regexp-to-ast-0.4.0.tgz", @@ -939,6 +1012,29 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.41.tgz", "integrity": "sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g==" }, + "@types/prop-types": { + "version": "15.7.4", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", + "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", + "dev": true + }, + "@types/react": { + "version": "17.0.39", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.39.tgz", + "integrity": "sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, "ansi-escapes": { "version": "4.3.1", "resolved": "http://localhost:4873/ansi-escapes/-/ansi-escapes-4.3.1.tgz", @@ -1075,6 +1171,12 @@ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, + "csstype": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", + "dev": true + }, "csv-writer": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/csv-writer/-/csv-writer-1.6.0.tgz", @@ -1235,6 +1337,12 @@ "lodash": "4.17.15" } }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, "jsonfile": { "version": "6.1.0", "resolved": "http://localhost:4873/jsonfile/-/jsonfile-6.1.0.tgz", @@ -1264,6 +1372,15 @@ "chalk": "^4.0.0" } }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -1323,6 +1440,12 @@ "resolved": "http://localhost:4873/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, "onetime": { "version": "5.1.2", "resolved": "http://localhost:4873/onetime/-/onetime-5.1.2.tgz", @@ -1351,6 +1474,16 @@ "resolved": "http://localhost:4873/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" }, + "react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, "regexp-to-ast": { "version": "0.4.0", "resolved": "http://localhost:4873/regexp-to-ast/-/regexp-to-ast-0.4.0.tgz", diff --git a/package.json b/package.json index 8e7dd7b..c0f5b9b 100644 --- a/package.json +++ b/package.json @@ -35,5 +35,9 @@ "terminal-link": "^2.1.1", "ts-morph": "^8.2.0", "ts-node": "^9.1.1" + }, + "devDependencies": { + "@types/react": "^17.0.39", + "react": "^17.0.2" } } diff --git a/src/core/enum/syntax-kind.enum.ts b/src/core/enum/syntax-kind.enum.ts index 8adb52f..950d82e 100644 --- a/src/core/enum/syntax-kind.enum.ts +++ b/src/core/enum/syntax-kind.enum.ts @@ -193,6 +193,7 @@ export enum SyntaxKind { JsxAttributes = 'JsxAttributes', JsxClosingElement = 'JsxClosingElement', JsxClosingFragment = 'JsxClosingFragment', + JsxComponent = 'JsxComponent', JsxElement = 'JsxElement', JsxExpression = 'JsxExpression', JsxFragment = 'JsxFragment', diff --git a/src/core/mocks/tsx/tsx.mock.tsx b/src/core/mocks/tsx/tsx.mock.tsx new file mode 100644 index 0000000..25dec20 --- /dev/null +++ b/src/core/mocks/tsx/tsx.mock.tsx @@ -0,0 +1,10 @@ +import React from 'react'; + +const component = () => { + console.log('TSX MOCKKKK'); + return ( +
+

Some text

+
+ ) +} diff --git a/src/index-debug.ts b/src/index-debug.ts index a88df03..731d35d 100644 --- a/src/index-debug.ts +++ b/src/index-debug.ts @@ -15,7 +15,7 @@ const ENABLE_CONSOLE_REPORT = ARGS[3] === 'true'; let FRAMEWORK = ARGS[5] ?? undefined; export async function startDebug(): Promise { - const pathToAnalyse = `${process.cwd()}/src/core/mocks`; + const pathToAnalyse = `${process.cwd()}/src/core/mocks/tsx`; FRAMEWORK = 'react'; Options.setOptions(process.cwd(), pathToAnalyse, __dirname); if (!ENABLE_CONSOLE_REPORT) { diff --git a/src/index.ts b/src/index.ts index 7528e0d..1185f7e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,7 +19,7 @@ const ENABLE_MARKDOWN_REPORT = ARGS[2] === 'true'; const ENABLE_CONSOLE_REPORT = ARGS[3] === 'true'; const ENABLE_REFACTORING = ARGS[4] === 'true'; let FRAMEWORK = ARGS[5] ?? undefined; -const DEBUG = false; +const DEBUG = true; let pathToAnalyse: string; if (path.isAbsolute(PATH_TO_ANALYSE)) { diff --git a/src/json-ast-to-reports/interfaces/evaluate.interface.ts b/src/json-ast-to-reports/interfaces/evaluate.interface.ts index aaebb48..0640a9f 100644 --- a/src/json-ast-to-reports/interfaces/evaluate.interface.ts +++ b/src/json-ast-to-reports/interfaces/evaluate.interface.ts @@ -6,7 +6,7 @@ import { CpxFactors } from '../../core/models/cpx-factor/cpx-factors.model'; export interface Evaluate { cpxFactors: CpxFactors; // The complexity factors of the object - cyclomaticCpx: number; // The cyclomatic complexity of the object + cyclomaticCpx?: number; // The cyclomatic complexity of the object evaluate:() => void; // The evaluation method } diff --git a/src/json-ast-to-reports/models/ast/ast-file.model.ts b/src/json-ast-to-reports/models/ast/ast-file.model.ts index ca654ed..5eb3275 100644 --- a/src/json-ast-to-reports/models/ast/ast-file.model.ts +++ b/src/json-ast-to-reports/models/ast/ast-file.model.ts @@ -16,10 +16,12 @@ import { DepthCpx } from '../../../core/models/cpx-factor/depth-cpx.model'; import { addObjects } from '../../../core/services/tools.service'; import { AstMethodOrOutsideNode, isAstMethod } from '../../types/ast-method-or-outside-node.type'; import { CpxLevel } from '../../enums/cpx-level.enum'; +import { AstJsxComponent } from './ast-jsx.model'; export class AstFile implements AstFileInterface, Evaluate, Logg { private _astFolder?: AstFolder = undefined; // The AstFolder which includes this AstFile + private _astJsxComponents: AstJsxComponent[]; private _astMethods?: AstMethod[] = []; // The AstMethods included in this AstFile private _astNode?: AstNode = undefined; // The AstNode corresponding to the file itself private _astNodes?: AstNode[] = undefined; // Array of all the AstNodes which are children of this.AstNode (including itself) @@ -181,6 +183,16 @@ export class AstFile implements AstFileInterface, Evaluate, Logg { } + get astJsxComponents(): AstJsxComponent[] { + return this._astJsxComponents; + } + + + set astJsxComponents(astJsxComponents: AstJsxComponent[]) { + this._astJsxComponents = astJsxComponents; + } + + get name(): string { return this._name; } @@ -226,6 +238,7 @@ export class AstFile implements AstFileInterface, Evaluate, Logg { for (const methodOrOutsideNode of methodsAndOutsideNodes) { this.evaluateMethodOrOutsideNode(methodOrOutsideNode); } + this.getJsx(); } @@ -236,6 +249,11 @@ export class AstFile implements AstFileInterface, Evaluate, Logg { } + private getJsx(): any[] { + return []; + } + + private evaluateMethodOrOutsideNode(methodOrOutsideNode: AstMethodOrOutsideNode): void { methodOrOutsideNode.evaluate(); this.cpxFactors = this.cpxFactors.add(methodOrOutsideNode.cpxFactors); diff --git a/src/json-ast-to-reports/models/ast/ast-jsx.model.ts b/src/json-ast-to-reports/models/ast/ast-jsx.model.ts new file mode 100644 index 0000000..92e0ca5 --- /dev/null +++ b/src/json-ast-to-reports/models/ast/ast-jsx.model.ts @@ -0,0 +1,277 @@ +import { AstNode } from './ast-node.model'; +import { Code } from '../code/code.model'; +import { Ast } from '../../services/ast/ast.service'; +import { Evaluate } from '../../interfaces/evaluate.interface'; +import { CpxFactors } from '../../../core/models/cpx-factor/cpx-factors.model'; +import { ComplexityType } from '../../enums/complexity-type.enum'; +import { CodeLine } from '../code/code-line.model'; +import { cpxFactors } from '../../../core/const/cpx-factors'; +import { FactorCategory } from '../../enums/factor-category.enum'; +import { Options } from '../../../core/models/options.model'; +import { CpxLevel } from '../../enums/cpx-level.enum'; + +/** + * Element of the AstNode structure corresponding to a given method + */ +export class AstJsxComponent implements Evaluate { + + private _astNode?: AstNode = undefined; // The AST of the method itself + private _codeLines?: CodeLine[] = []; // The array of CodeLine of the AstMethod (elements of the array of CodeLine of the corresponding AstFile) + private _cognitiveLevel: CpxLevel = CpxLevel.LOW; // The cognitive level of the method + private _cpxFactors?: CpxFactors = undefined; // The complexity factors of the AstMethod + private _cpxIndex = undefined; // The complexity index of the method + private _displayedCode?: Code = undefined; // The code to display in the report + private _maxLineLength ?= 0; // The max length of the lines of the code + private _name: string = undefined; // The name of the method + + + + // --------------------------------------------------------------------------------- + // Getters and setters + // --------------------------------------------------------------------------------- + + + get astNode(): AstNode { + return this._astNode; + } + + + set astNode(astNode: AstNode) { + this._astNode = astNode; + } + + + get codeLines(): CodeLine[] { + return this._codeLines; + } + + + set codeLines(codeLines: CodeLine[]) { + this._codeLines = codeLines; + } + + + get cognitiveLevel(): CpxLevel { + return this._cognitiveLevel; + } + + + set cognitiveLevel(cognitiveStatus: CpxLevel) { + this._cognitiveLevel = cognitiveStatus; + } + + + get cpxFactors(): CpxFactors { + return this._cpxFactors; + } + + + set cpxFactors(cpxFactors: CpxFactors) { + this._cpxFactors = cpxFactors; + } + + + get cpxIndex(): number { + return this._cpxIndex ?? this.cpxFactors.total; + } + + + get displayedCode(): Code { + return this._displayedCode; + } + + + get end(): number { + return this.astNode.end; + } + + + get maxLineLength(): number { + if (this._maxLineLength) { + return this._maxLineLength; + } + this._maxLineLength = Math.max(...this.codeLines?.map(l => l.end - l.start)); + return this._maxLineLength; + } + + + get name(): string { + if (this._name) { + return this._name; + } + this._name = this._astNode.name; + return this._name; + } + + + set name(name: string) { + this._name = name; + } + + + get pos() { + return this.astNode?.pos; + } + + + get start() { + return this.astNode?.start; + } + + + + // --------------------------------------------------------------------------------- + // Other methods + // --------------------------------------------------------------------------------- + + + + /** + * Creates the displayed code of this AstMethod and evaluates its complexity + */ + evaluate(): void { + this.createDisplayedCodeAndCalculateCpxFactors(); + // LogService.logMethod(this); + this.cognitiveLevel = this.getComplexityStatus(ComplexityType.COGNITIVE); + } + + + /** + * Gets the complexity status of the method for a given complexity type + * @param cpxType + */ + getComplexityStatus(cpxType: ComplexityType): CpxLevel { + let status = CpxLevel.MEDIUM; + if (cpxType === ComplexityType.COGNITIVE && this.cpxIndex <= Options.cognitiveCpx.warningThreshold) { + status = CpxLevel.LOW; + } else if (cpxType === ComplexityType.COGNITIVE && Math.round(this.cpxIndex) > Options.cognitiveCpx.errorThreshold) { + status = CpxLevel.HIGH; + } + return status; + } + + + /** + * Creates the method's code to display, with comments + * @param astNode // The AstNode to analyse (by default: the AstNode associated to this AstMethod) + */ + createDisplayedCodeAndCalculateCpxFactors(astNode: AstNode = this.astNode): void { + this.setDisplayedCodeLines(); + this.setDeclarationCpxFactors(); + this.setCpxFactorsToDisplayedCode(astNode, false); + this._displayedCode.setLinesDepthAndNestingCpx(); + this.addCommentsToDisplayedCode(); + this.calculateCpxFactors(); + this._displayedCode.setTextWithLines(); + } + + + /** + * Creates the Code object corresponding to the code to display + */ + private setDisplayedCodeLines(): void { + this._displayedCode = new Code(); + for (const line of this.codeLines) { + const displayedLine = new CodeLine(); + displayedLine.issue = line.issue; + displayedLine.end = line.end; + displayedLine.start = line.start; + displayedLine.text = line.text; + displayedLine.text = this.getDisplayedLineText(displayedLine); + this._displayedCode.lines.push(displayedLine); + } + } + + + /** + * Returns the text to display for a given line. Removes characters of the first and the last lines which are not inside the AstMethod + * @param line // The line to display + */ + private getDisplayedLineText(line: CodeLine): string { + let text = line.text; + if (line.issue === this.codeLines[0]?.issue) { + const firstCharPosition = this.start - line.start; + const indentation = text.slice(0, text.length - text.trimLeft().length) + text = `\n${indentation}${text.slice(firstCharPosition)}`; + } + if (line.issue === this.codeLines[this.codeLines.length - 1]?.issue) { + const lastCharPosition = this.end - line.start; + text = text.slice(0, lastCharPosition); + } + return text; + } + + + private setDeclarationCpxFactors(): void { + this.increaseLineCpxFactors(this.astNode, this._displayedCode.getLine(this.astNode.lineStart)); + this._displayedCode.getLine(this.astNode.lineStart).astNodes.push(this.astNode); + } + + + /** + * Calculates the complexity factors of each CodeLine + * @param astNode // The AstNode of the method + * @param startedUncommentedLines // Param for recursion (checks if the current line is the first uncommented one) + */ + private setCpxFactorsToDisplayedCode(astNode: AstNode, startedUncommentedLines = false): void { + for (const childAst of astNode.children) { + let issue = Math.max(childAst.lineStart, this.codeLines[0]?.issue); + const codeLine: CodeLine = this._displayedCode.lines.find(l => l.issue === issue); + if (Ast.isElseStatement(childAst)) { + childAst.cpxFactors.atomic.node = cpxFactors.atomic.node; + issue--; + } + this.increaseLineCpxFactors(childAst, codeLine); + this._displayedCode.getLine(issue).astNodes.push(childAst); + this.setCpxFactorsToDisplayedCode(childAst, startedUncommentedLines); + } + } + + + /** + * Adds the Complexity of a AstNode to its CodeLine + * @param astNode // The AstNode inside the line of code + * @param codeLine // The CodeLine containing the AstNode + */ + private increaseLineCpxFactors(astNode: AstNode, codeLine: CodeLine): void { + if (!codeLine.isCommented) { + codeLine.cpxFactors = codeLine.cpxFactors.add(astNode?.cpxFactors); + } + } + + + /** + * Adds information about complexity factors for each line of the displayed code + */ + private addCommentsToDisplayedCode(): void { + this._displayedCode.lines + .filter(line => line.cpxFactors.total > 0) + .forEach(line => { + let comment = `+${line.cpxFactors.total.toFixed(1)} Complexity index (+${line.cpxFactors.totalAtomic.toFixed(1)} ${FactorCategory.ATOMIC}`; + comment = line.cpxFactors.totalStructural > 0 ? `${comment}, +${line.cpxFactors.totalStructural} ${FactorCategory.STRUCTURAL}` : comment; + comment = line.cpxFactors.totalNesting > 0 ? `${comment}, +${line.cpxFactors.totalNesting} nesting` : comment; + comment = line.cpxFactors.totalTyping > 0 ? `${comment}, +${line.cpxFactors.totalTyping} typing` : comment; + comment = line.cpxFactors.totalAggregation > 0 ? `${comment}, +${line.cpxFactors.totalAggregation} ${FactorCategory.AGGREGATION}` : comment; + comment = line.cpxFactors.totalDepth > 0 ? `${comment}, +${line.cpxFactors.totalDepth} depth` : comment; + comment = line.cpxFactors.totalRecursion > 0 ? `${comment}, +${line.cpxFactors.totalRecursion} recursivity` : comment; + comment = line.cpxFactors.totalUse > 0 ? `${comment}, +${line.cpxFactors.totalUse} ${FactorCategory.USE}` : comment; + comment = `${comment})`; + this._displayedCode.getLine(line.issue).addComment(comment, this.maxLineLength); + }); + } + + + /** + * Calculates the Complexity Factors of the method + */ + private calculateCpxFactors(): void { + const lines: CodeLine[] = this._displayedCode?.lines; + if (lines.length === 0) { + this.createDisplayedCodeAndCalculateCpxFactors(); + } + this.cpxFactors = new CpxFactors(); + for (const line of this._displayedCode?.lines) { + this.cpxFactors = this.cpxFactors.add(line.cpxFactors); + } + } +} diff --git a/src/json-ast-to-reports/models/ast/ast-node.model.ts b/src/json-ast-to-reports/models/ast/ast-node.model.ts index ebbf462..c87e1c2 100644 --- a/src/json-ast-to-reports/models/ast/ast-node.model.ts +++ b/src/json-ast-to-reports/models/ast/ast-node.model.ts @@ -18,10 +18,12 @@ import { CpxFactorsInterface } from '../../../core/interfaces/cpx-factors.interf import { FactorCategory } from '../../enums/factor-category.enum'; import { TypingCpx } from '../../../core/models/cpx-factor/typing-cpx.model'; import { Options } from '../../../core/models/options.model'; +import { AstJsxComponent } from './ast-jsx.model'; export class AstNode implements AstNodeInterface, Evaluate, Logg { private _astFile?: AstFile = undefined; // The AstFile containing the AST node of the AstNode + private _astJsxComponent?: AstJsxComponent = undefined; // The method at the root of the current ast (if this ast is inside a method) private _astMethod?: AstMethod = undefined; // The method at the root of the current ast (if this ast is inside a method) private _astNodeService?: AstNodeService = new AstNodeService(); // The service managing AstNodes private _children?: AstNode[] = []; // The children AstNodes of the AstNode @@ -69,6 +71,16 @@ export class AstNode implements AstNodeInterface, Evaluate, Logg { } + get astJsxComponent(): AstJsxComponent { + return this._astJsxComponent; + } + + + set astJsxComponent(astJsxComponent: AstJsxComponent) { + this._astJsxComponent = astJsxComponent; + } + + get astMethod(): AstMethod { return this._astMethod; } diff --git a/src/json-ast-to-reports/services/ast/ast-file.service.ts b/src/json-ast-to-reports/services/ast/ast-file.service.ts index 8feccf7..4d68306 100644 --- a/src/json-ast-to-reports/services/ast/ast-file.service.ts +++ b/src/json-ast-to-reports/services/ast/ast-file.service.ts @@ -4,6 +4,7 @@ import { StatsService } from '../report/stats.service'; import { Stats } from '../../models/stats.model'; import { ComplexityType } from '../../enums/complexity-type.enum'; import { CpxLevel } from '../../enums/cpx-level.enum'; +import { AstJsxComponent } from '../../models/ast/ast-jsx.model'; /** * - AstFiles generation from Abstract Syntax AstNode of a file @@ -74,4 +75,10 @@ export class AstFileService extends StatsService { this._stats.subject = astFile.name; } + + getJsxElements(astFile: AstFile): AstJsxComponent[] { + console.log('GET JSXXXX') + return []; + } + } diff --git a/src/json-ast-to-reports/services/ast/ast.service.ts b/src/json-ast-to-reports/services/ast/ast.service.ts index 7b7ad35..4089bef 100644 --- a/src/json-ast-to-reports/services/ast/ast.service.ts +++ b/src/json-ast-to-reports/services/ast/ast.service.ts @@ -145,6 +145,14 @@ export class Ast { false; } + /** + * Checks if an AST node is a function or a method + * @param astNode + */ + static isJsxComponent(astNode: AstNode): boolean { + return astNode?.type === SyntaxKind.JsxComponent; + } + /** * Checks if an AST node is a function or a method * @param astNode diff --git a/src/json-ast-to-reports/services/init.service.ts b/src/json-ast-to-reports/services/init.service.ts index 3b83283..c725fc0 100644 --- a/src/json-ast-to-reports/services/init.service.ts +++ b/src/json-ast-to-reports/services/init.service.ts @@ -9,6 +9,7 @@ import { AstNodeService } from './ast/ast-node.service'; import { Ast } from './ast/ast.service'; import { AssignedFunctionsService } from './ast/assigned-functions.service'; import { OutsideCodeService } from './ast/outside-code.service'; +import { AstJsxComponent } from '../models/ast/ast-jsx.model'; /** * - AstFolders generation from Abstract Syntax Tree of a folder @@ -88,18 +89,30 @@ export class InitService { newAstFile.code = CodeService.getCode(astFileFromJsonAst.text); newAstFile.astNode = this.getFileAstNode(astFileFromJsonAst.astNode, newAstFile); newAstFile.astNodes = this.astNodeService.flatMapAstNodes(newAstFile.astNode, [newAstFile.astNode]); - newAstFile.astMethods = newAstFile.astNodes - .filter(e => { - return Ast.isFunctionOrMethod(e) - }) - .map(e => e.astMethod); + newAstFile.astMethods = this.getFunctionsOrMethods(newAstFile); + newAstFile.astJsxComponents = this.getJsxComponents(newAstFile); const functionsAssignedToVars: AstMethod[] = AssignedFunctionsService.getArrowFunctions(newAstFile.astNode); newAstFile.astMethods = newAstFile.astMethods.concat(functionsAssignedToVars); newAstFile.astOutsideNodes = OutsideCodeService.getOutsideNodes(newAstFile.astNode); + console.log('AST JSX COMPONENTSSSS', newAstFile.astJsxComponents); return newAstFile; } + private getFunctionsOrMethods(astFile: AstFile): AstMethod[] { + return astFile.astNodes + .filter(Ast.isFunctionOrMethod) + .map(e => e.astMethod); + } + + + private getJsxComponents(astFile: AstFile): AstJsxComponent[] { + return astFile.astNodes + .filter(Ast.isJsxComponent) + .map(e => e.astJsxComponent); + } + + /** * Generates the AstNode of a given AstFile with the astNode property in the JsonAst object * @param astNodeFromJsonAst // The astNode property in the JsonAst object @@ -152,6 +165,13 @@ export class InitService { newAstNode.text = astNodeFromJsonAst.text; newAstNode.type = astNodeFromJsonAst.type; newAstNode.children = this.generateAstNodes(astNodeFromJsonAst.children, newAstNode); + this.setAstMethodProperty(astNodeFromJsonAst, astParentNode, newAstNode); + this.setAstJsxComponentProperty(astNodeFromJsonAst, astParentNode, newAstNode); + return newAstNode; + } + + + private setAstMethodProperty(astNodeFromJsonAst: any, astParentNode: AstNode, newAstNode: AstNode): void { if (Ast.isFunctionOrMethod(astNodeFromJsonAst)) { if (!newAstNode.name && newAstNode.firstSon?.kind === SyntaxKind.Identifier) { newAstNode.name = newAstNode.children[0].name; @@ -160,7 +180,16 @@ export class InitService { } else { newAstNode.astMethod = astParentNode?.astMethod; } - return newAstNode; + + } + + + private setAstJsxComponentProperty(astNodeFromJsonAst: any, astParentNode: AstNode, newAstNode: AstNode): void { + if (Ast.isJsxComponent(astNodeFromJsonAst)) { + newAstNode.astJsxComponent = this.generateAstJsxComponent(newAstNode); + } else { + newAstNode.astJsxComponent = astParentNode?.astJsxComponent; + } } @@ -177,6 +206,19 @@ export class InitService { } + /** + * Generates the AstMethod corresponding to an AstNode with kind corresponding to a FunctionDeclaration or a MethodDeclaration + * @param astJsxComponentNode // The AstNode which corresponds to a FunctionDeclaration or a MethodDeclaration + */ + private generateAstJsxComponent(astJsxComponentNode: AstNode): AstJsxComponent { + const astJsxComponent = new AstJsxComponent(); + astJsxComponent.astNode = astJsxComponentNode; + astJsxComponent.astNode.text = this.astNodeService.getCode(astJsxComponentNode); + astJsxComponent.codeLines = astJsxComponentNode.astFile?.code?.lines?.slice(astJsxComponentNode.linePos - 1, astJsxComponentNode.lineEnd); + return astJsxComponent; + } + + /** * Returns the path without slash corresponding to the "path" property of the JsonAst object * @param jsonAstFolder diff --git a/src/languages-to-json-ast/ts/services/ast-file-generation.service.ts b/src/languages-to-json-ast/ts/services/ast-file-generation.service.ts index e179f9c..6abbf4d 100644 --- a/src/languages-to-json-ast/ts/services/ast-file-generation.service.ts +++ b/src/languages-to-json-ast/ts/services/ast-file-generation.service.ts @@ -10,7 +10,6 @@ import { Ts } from './ts.service'; import { randomString } from '../../../core/services/tools.service'; import { Options } from '../../../core/models/options.model'; import { ReactService } from '../specific/react/react.service'; -import { isJsx } from '../utils/ast.util'; /** * - AstFiles generation from their Abstract Syntax Tree (AST) @@ -69,14 +68,12 @@ export class AstFileGenerationService { start: node.getStart() }; astNode = this.addTypeAndCpxFactors(node, astNode); - if (!isJsx(node)) { - node.forEachChild((childNode: Node) => { - if (!astNode.children) { - astNode.children = []; - } - astNode.children.push(this.createAstNodeChildren(childNode)); - }); - } + node.forEachChild((childNode: Node) => { + if (!astNode.children) { + astNode.children = []; + } + astNode.children.push(this.createAstNodeChildren(childNode)); + }); return astNode; } @@ -105,6 +102,9 @@ export class AstFileGenerationService { if (Ts.isVarStatement(node)) { astNode.type = Ts.getVarStatementType(node); } + if (Ts.isJsxComponent(node)) { + astNode.type = SyntaxKind.JsxComponent; + } return astNode; } diff --git a/src/languages-to-json-ast/ts/services/ts.service.ts b/src/languages-to-json-ast/ts/services/ts.service.ts index e7c7c45..9941b2e 100644 --- a/src/languages-to-json-ast/ts/services/ts.service.ts +++ b/src/languages-to-json-ast/ts/services/ts.service.ts @@ -1,5 +1,13 @@ import { KindAliases } from '../../globals.const'; -import { Expression, Node, ParameterDeclaration, SyntaxKind, VariableDeclaration, VariableStatement } from 'ts-morph'; +import { + Expression, + JsxElement, + Node, + ParameterDeclaration, + SyntaxKind, + VariableDeclaration, + VariableStatement +} from 'ts-morph'; import { isFunctionKind } from '../types/function-kind.type'; import { FunctionNode } from '../types/function-node.type'; @@ -83,6 +91,21 @@ export class Ts { } + static isJsxComponent(node: Node): node is JsxElement { + return this.isJsxElement(node) && !this.hasJsxElementAncestor(node); + } + + + static isJsxElement(node: Node): node is JsxElement { + return node.getKind() === SyntaxKind.JsxElement; + } + + + static hasJsxElementAncestor(node: Node): boolean { + return !!node.getFirstAncestorByKind(SyntaxKind.JsxElement); + } + + static getFunctionType(functionNode: FunctionNode): string { if (!this.hasCompilerNodeType(functionNode)) { return undefined; diff --git a/src/languages-to-json-ast/ts/utils/ast.util.ts b/src/languages-to-json-ast/ts/utils/ast.util.ts deleted file mode 100644 index 1b42ccb..0000000 --- a/src/languages-to-json-ast/ts/utils/ast.util.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Node } from 'ts-morph'; - - -export function isJsx(node: Node): boolean { - return node?.getKindName()?.slice(0, 3) === 'Jsx'; -}