From 5f86372c7723c5e90603c41dd7e68a3c783b3b87 Mon Sep 17 00:00:00 2001 From: sgb-io Date: Sat, 9 Jan 2021 11:35:40 +0000 Subject: [PATCH 1/3] Refactor utils Removed some `fs` calls that were not logically needed, split apart the utils into smalle r, separate pieces --- generated/avg-maintainability.svg | 10 +- src/codehawk.ts | 2 +- src/dependencies.ts | 2 +- src/traverseProject.ts | 32 ++++-- src/util.test.ts | 2 +- src/util.ts | 169 ------------------------------ src/utils/blocklist.ts | 32 ++++++ src/utils/entities.ts | 127 ++++++++++++++++++++++ src/utils/index.ts | 3 + src/utils/tree.ts | 16 +++ 10 files changed, 211 insertions(+), 184 deletions(-) delete mode 100644 src/util.ts create mode 100644 src/utils/blocklist.ts create mode 100644 src/utils/entities.ts create mode 100644 src/utils/index.ts create mode 100644 src/utils/tree.ts diff --git a/generated/avg-maintainability.svg b/generated/avg-maintainability.svg index 0f9ec6d..2fdca48 100644 --- a/generated/avg-maintainability.svg +++ b/generated/avg-maintainability.svg @@ -1,5 +1,5 @@ - - maintainability (avg): 54.30 + + maintainability (avg): 55.96 @@ -7,14 +7,14 @@ - + \ No newline at end of file diff --git a/src/codehawk.ts b/src/codehawk.ts index d05f6ee..634028c 100644 --- a/src/codehawk.ts +++ b/src/codehawk.ts @@ -16,7 +16,7 @@ import type { AssembledOptions, } from './types' import { buildOptions, getConfiguration } from './options' -import { flattenEntireTree } from './util' +import { flattenEntireTree } from './utils' import { generateBadge } from './badge' export interface ResultsSummary { diff --git a/src/dependencies.ts b/src/dependencies.ts index b2972a5..756c305 100644 --- a/src/dependencies.ts +++ b/src/dependencies.ts @@ -1,6 +1,6 @@ import path from 'path' import slash from 'slash' -import { flattenEntireTree } from './util' +import { flattenEntireTree } from './utils' import type { AnalyzedEntity, AnalyzedFile } from './types' // Gathers all the dependencies as a flat array of strings across all analyzed files diff --git a/src/traverseProject.ts b/src/traverseProject.ts index b03479f..f05066d 100644 --- a/src/traverseProject.ts +++ b/src/traverseProject.ts @@ -1,7 +1,7 @@ import * as fs from 'fs' import * as path from 'path' import slash from 'slash' -import { getFsEntity, shouldSeeEntity, shouldAnalyzeEntity } from './util' +import { getFsEntity, shouldSeeEntity, shouldAnalyzeEntity } from './utils' import type { AssembledOptions, ParsedEntity, @@ -17,15 +17,33 @@ export const walkSync = ( ): ParsedEntity[] => { const fileList: ParsedEntity[] = [] const items = fs.readdirSync(dir) - const visibleEntities = items.filter((i) => shouldSeeEntity(dir, i, options)) + const parsedEntities = items.map((filename) => ({ + filename, + fullPath: path.join(dir, filename), + entity: getFsEntity(path.join(dir, filename)), + relativeDir: slash(dir).replace(slash(process.cwd()), '') + })) + const visibleEntities = parsedEntities.filter((item) => shouldSeeEntity({ + filename: item.filename, + dir, + fullPath: item.fullPath, + entity: item.entity, + options, + relativeDir: item.relativeDir + })) visibleEntities.forEach((item) => { - const fullPath = path.join(dir, item) - const entity = getFsEntity(fullPath) + const { fullPath, filename, entity, relativeDir } = item const baseParsedEntity = { - fullPath: slash(fullPath), - filename: item, - shouldAnalyze: shouldAnalyzeEntity(dir, item, options), + fullPath: slash(item.fullPath), + filename, + shouldAnalyze: shouldAnalyzeEntity({ + entity, + filename, + fullPath, + options, + relativeDir, + }), } if (entity.isDirectory()) { diff --git a/src/util.test.ts b/src/util.test.ts index 8df2537..6ecae6c 100644 --- a/src/util.test.ts +++ b/src/util.test.ts @@ -1,4 +1,4 @@ -import { isBlocklisted } from './util' +import { isBlocklisted } from './utils' import { buildOptions } from './options' const options = buildOptions({ diff --git a/src/util.ts b/src/util.ts deleted file mode 100644 index 0845dd6..0000000 --- a/src/util.ts +++ /dev/null @@ -1,169 +0,0 @@ -import * as fs from 'fs' -import * as path from 'path' -import slash from 'slash' -import isDotfile from 'is-dotfile' -import isDotdir from 'is-dotdir' -import type { AssembledOptions, AnalyzedEntity, AnyAnalyzedFile } from './types' - -const shouldSkip = ( - relativeDir: string, - skipDirectories: string[] -): boolean => { - for (let i = 0; i < skipDirectories.length; i += 1) { - if (relativeDir.startsWith(skipDirectories[i])) { - return true - } - } - - return false -} - -export const isBlocklisted = ( - relativeDir: string, - filename: string, - options: AssembledOptions -): boolean => { - const { excludeDirectories, excludeFilenames, excludeExact } = options - - // Check for blocklisted directories - for (let i = 0; i < excludeDirectories.length; i += 1) { - if (relativeDir.startsWith(excludeDirectories[i])) { - return true - } - } - - // Check for blocklisted filename matches - for (let i = 0; i < excludeFilenames.length; i += 1) { - if (filename.includes(excludeFilenames[i])) { - return true - } - } - - // Check for exact matches - for (let i = 0; i < excludeExact.length; i += 1) { - if (excludeExact[i] === `${relativeDir}/${filename}`) { - return true - } - } - - return false -} - -export const getFsEntity = (fullPath: string): fs.Stats | null => { - let dirent = null - - try { - dirent = fs.statSync(fullPath) - } catch (e) { - // Ingore the error - // Scenarios this catches: symlinks or otherwise inaccessible directories - } - - return dirent -} - -export const flattenEntireTree = ( - items: AnalyzedEntity[] -): T[] => { - let flattened: T[] = [] - items.forEach((item) => { - if (item.type === 'dir') { - flattened = flattened.concat(flattenEntireTree(item.files)) - } else { - flattened.push(item as T) - } - }) - - return flattened -} - -// Should the dir/file show at all in results? -export const shouldSeeEntity = ( - dir: string, - item: string, - options: AssembledOptions -): boolean => { - const fullPath = path.join(dir, item) - const entity = getFsEntity(fullPath) - const relativeDir = slash(dir).replace(slash(process.cwd()), '') - - // Is a symlink, or other un-readable entity? - if (!entity) { - return false - } - - // Is a directory? - if (entity.isDirectory()) { - // Exclude dot directories, allow all others - const dotDir = isDotdir(fullPath) || isDotdir(slash(fullPath)) - const dotFile = isDotfile(fullPath) || isDotfile(slash(fullPath)) - if (dotDir || dotFile) { - return false - } - - return true - } - - // Is a dotfile? - const dotFile = isDotfile(fullPath) || isDotfile(slash(fullPath)) - if (dotFile) { - return false - } - - // Is codehawk config? - if (item === 'codehawk.json') { - return false - } - - // Skip directories according to config - if (shouldSkip(relativeDir, options.skipDirectories)) { - return false - } - - return true -} - -// Should the dir/file have complexity analysis run? -export const shouldAnalyzeEntity = ( - dir: string, - item: string, - options: AssembledOptions -): boolean => { - const fullPath = path.join(dir, item) - const entity = getFsEntity(fullPath) - const filename = path.basename(fullPath) - const extension = path.extname(filename) - const relativeDir = slash(dir).replace(slash(process.cwd()), '') - - // Is a symlink, or other un-readable entity? - if (!entity) { - return false - } - - // Is a directory? - if (entity.isDirectory()) { - // Exclude dot directories, allow all others - const dotDir = isDotdir(fullPath) - const dotFile = isDotfile(fullPath) || isDotfile(slash(fullPath)) - if (dotDir || dotFile) { - return false - } - - return true - } - - const dotFile = isDotfile(fullPath) || isDotfile(slash(fullPath)) - if (dotFile) { - return false - } - - if (!options.extensions.includes(extension)) { - return false - } - - if (isBlocklisted(relativeDir, filename, options)) { - return false - } - - return true -} diff --git a/src/utils/blocklist.ts b/src/utils/blocklist.ts new file mode 100644 index 0000000..e15bc73 --- /dev/null +++ b/src/utils/blocklist.ts @@ -0,0 +1,32 @@ +import type { AssembledOptions } from '../types' + +export const isBlocklisted = ( + relativeDir: string, + filename: string, + options: AssembledOptions +): boolean => { + const { excludeDirectories, excludeFilenames, excludeExact } = options + + // Check for blocklisted directories + for (let i = 0; i < excludeDirectories.length; i += 1) { + if (relativeDir.startsWith(excludeDirectories[i])) { + return true + } + } + + // Check for blocklisted filename matches + for (let i = 0; i < excludeFilenames.length; i += 1) { + if (filename.includes(excludeFilenames[i])) { + return true + } + } + + // Check for exact matches + for (let i = 0; i < excludeExact.length; i += 1) { + if (excludeExact[i] === `${relativeDir}/${filename}`) { + return true + } + } + + return false +} diff --git a/src/utils/entities.ts b/src/utils/entities.ts new file mode 100644 index 0000000..43ca1bc --- /dev/null +++ b/src/utils/entities.ts @@ -0,0 +1,127 @@ +import * as fs from 'fs' +import * as path from 'path' +import slash from 'slash' +import isDotfile from 'is-dotfile' +import isDotdir from 'is-dotdir' +import type { AssembledOptions } from '../types' +import { isBlocklisted } from './blocklist' + +const shouldSkipDir = ( + relativeDir: string, + skipDirectories: string[] +): boolean => { + for (let i = 0; i < skipDirectories.length; i += 1) { + if (relativeDir.startsWith(skipDirectories[i])) { + return true + } + } + + return false +} + +const isDotEntity = (fullPath: string): boolean => { + const dotDir = isDotdir(fullPath) || isDotdir(slash(fullPath)) + const dotFile = isDotfile(fullPath) || isDotfile(slash(fullPath)) + return dotDir || dotFile +} + +export const getFsEntity = (fullPath: string): fs.Stats | null => { + let dirent = null + + try { + dirent = fs.statSync(fullPath) + } catch (e) { + // Ingore the error + // Scenarios this catches: symlinks or otherwise inaccessible directories + } + + return dirent +} + +// Should the dir/file show at all in results? +export const shouldSeeEntity = ({ + dir, + entity, + filename, + fullPath, + options, + relativeDir, +}: { + dir: string + entity: fs.Stats + filename: string + fullPath: string + options: AssembledOptions + relativeDir: string +}): boolean => { + // Is a symlink, or other un-readable entity? + if (!entity) { + return false + } + + // Exclude dot directories/files + if (isDotEntity(fullPath)) { + return false + } + + // Is a directory? + if (entity.isDirectory()) { + return true + } + + // Is codehawk config? + if (filename === 'codehawk.json') { + return false + } + + // Should the directory be completely skipped according to user options? + if (shouldSkipDir(relativeDir, options.skipDirectories)) { + return false + } + + return true +} + +// Should the dir/file have complexity analysis run? +export const shouldAnalyzeEntity = ({ + entity, + filename, + fullPath, + options, + relativeDir, +}: { + entity: fs.Stats + filename: string + fullPath: string + options: AssembledOptions + relativeDir: string +}): boolean => { + const extension = path.extname(filename) + + // Is a symlink, or other un-readable entity? + if (!entity) { + return false + } + + // Exclude dot directories/files + if (isDotEntity(fullPath)) { + return false + } + + // Is a directory? + if (entity.isDirectory()) { + return true + } + + // Is the extension included in the options? + if (!options.extensions.includes(extension)) { + return false + } + + // Is the path/filename excluded by user options? + if (isBlocklisted(relativeDir, filename, options)) { + return false + } + + return true +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..8d01b48 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from './entities' +export * from './blocklist' +export * from './tree' diff --git a/src/utils/tree.ts b/src/utils/tree.ts new file mode 100644 index 0000000..4a4f10b --- /dev/null +++ b/src/utils/tree.ts @@ -0,0 +1,16 @@ +import type { AnalyzedEntity, AnyAnalyzedFile } from '../types' + +export const flattenEntireTree = ( + items: AnalyzedEntity[] +): T[] => { + let flattened: T[] = [] + items.forEach((item) => { + if (item.type === 'dir') { + flattened = flattened.concat(flattenEntireTree(item.files)) + } else { + flattened.push(item as T) + } + }) + + return flattened +} From 6848ae9497a68a1f658aaeb2b3b13ccc33c9cd55 Mon Sep 17 00:00:00 2001 From: sgb-io Date: Sat, 9 Jan 2021 11:43:07 +0000 Subject: [PATCH 2/3] Break apart the code more --- generated/avg-maintainability.svg | 8 +-- generated/worst-maintainability.svg | 8 +-- src/codehawk.ts | 80 +++-------------------------- src/types/codehawk.ts | 6 +++ src/utils/index.ts | 3 +- src/utils/results.ts | 44 ++++++++++++++++ src/utils/tree.ts | 28 +++++++++- 7 files changed, 94 insertions(+), 83 deletions(-) create mode 100644 src/utils/results.ts diff --git a/generated/avg-maintainability.svg b/generated/avg-maintainability.svg index 2fdca48..2238685 100644 --- a/generated/avg-maintainability.svg +++ b/generated/avg-maintainability.svg @@ -1,5 +1,5 @@ - - maintainability (avg): 55.96 + + maintainability (avg): 56.28 @@ -13,8 +13,8 @@ \ No newline at end of file diff --git a/generated/worst-maintainability.svg b/generated/worst-maintainability.svg index a415732..523df7e 100644 --- a/generated/worst-maintainability.svg +++ b/generated/worst-maintainability.svg @@ -1,5 +1,5 @@ - - maintainability (worst): 45.24 + + maintainability (worst): 46.65 @@ -13,8 +13,8 @@ \ No newline at end of file diff --git a/src/codehawk.ts b/src/codehawk.ts index 634028c..fdc0221 100644 --- a/src/codehawk.ts +++ b/src/codehawk.ts @@ -5,25 +5,20 @@ import { analyzeFile, calculateComplexity } from './analyze' import { getFileContents, walkSync } from './traverseProject' import { getTimesDependedOn, getProjectDeps } from './dependencies' import type { - ParsedEntity, - ParsedFile, - AnalyzedFile, AnalyzedDirectory, AnalyzedEntity, + AnalyzedFile, + AssembledOptions, + FullyAnalyzedDirectory, FullyAnalyzedEntity, FullyAnalyzedFile, - FullyAnalyzedDirectory, - AssembledOptions, + ParsedEntity, + ParsedFile, + ResultsSummary, } from './types' import { buildOptions, getConfiguration } from './options' -import { flattenEntireTree } from './utils' import { generateBadge } from './badge' - -export interface ResultsSummary { - average: number - median: number - worst: number -} +import { getResultsAsList, getResultsSummary } from './utils' export interface Results { options: AssembledOptions @@ -153,66 +148,5 @@ const analyzeProject = (rawPath: string, isCliContext?: boolean): Results => { } } -const getResultsAsList = ( - analyzedEntities: FullyAnalyzedEntity[], - limit?: number -): FullyAnalyzedFile[] => { - const flatFileResults: FullyAnalyzedFile[] = flattenEntireTree< - FullyAnalyzedFile - >(analyzedEntities) - .filter((entity) => { - return entity.type === 'file' && !!entity.complexityReport - }) - // Sort by codehawk score, ascending (most complex files are first in the list) - .sort((entityA, entityB) => { - return ( - entityA.complexityReport.codehawkScore - - entityB.complexityReport.codehawkScore - ) - }) - - return limit ? flatFileResults.slice(0, limit) : flatFileResults -} - -const getMedian = (numbers: number[]): number => { - const sorted = numbers.slice().sort((a, b) => a - b) - const middle = Math.floor(sorted.length / 2) - - if (sorted.length % 2 === 0) { - return (sorted[middle - 1] + sorted[middle]) / 2 - } - - return sorted[middle] -} - -const getResultsSummary = ( - resultsAsList: FullyAnalyzedFile[] -): ResultsSummary => { - const allScores: number[] = resultsAsList.reduce((arr: number[], current) => { - if (current.complexityReport) { - arr.push(current.complexityReport.codehawkScore) - } - return arr - }, []) - const total = allScores.reduce((total: number, score) => { - return total + score - }, 0) - const worst = allScores.reduce((worst: number, score) => { - if (score < worst) { - return score - } - return worst - }, 100) // Start with max maintainability, work downwards - - const average = total / allScores.length - const median = getMedian(allScores) - - return { - average, - median, - worst, - } -} - // Public APIs export { calculateComplexity, analyzeProject, generateBadge } diff --git a/src/types/codehawk.ts b/src/types/codehawk.ts index 5dcb5b1..f922060 100644 --- a/src/types/codehawk.ts +++ b/src/types/codehawk.ts @@ -139,4 +139,10 @@ export interface CodehawkComplexityResult extends ComplexityResult { // After coverage mapping export interface CompleteCodehawkComplexityResult extends CodehawkComplexityResult { coverage: string +} + +export interface ResultsSummary { + average: number + median: number + worst: number } \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts index 8d01b48..4f03a1b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,4 @@ -export * from './entities' export * from './blocklist' +export * from './entities' +export * from './results' export * from './tree' diff --git a/src/utils/results.ts b/src/utils/results.ts new file mode 100644 index 0000000..eebb838 --- /dev/null +++ b/src/utils/results.ts @@ -0,0 +1,44 @@ +import type { + FullyAnalyzedFile, + ResultsSummary, +} from '../types' + +const getMedian = (numbers: number[]): number => { + const sorted = numbers.slice().sort((a, b) => a - b) + const middle = Math.floor(sorted.length / 2) + + if (sorted.length % 2 === 0) { + return (sorted[middle - 1] + sorted[middle]) / 2 + } + + return sorted[middle] +} + +export const getResultsSummary = ( + resultsAsList: FullyAnalyzedFile[] +): ResultsSummary => { + const allScores: number[] = resultsAsList.reduce((arr: number[], current) => { + if (current.complexityReport) { + arr.push(current.complexityReport.codehawkScore) + } + return arr + }, []) + const total = allScores.reduce((total: number, score) => { + return total + score + }, 0) + const worst = allScores.reduce((worst: number, score) => { + if (score < worst) { + return score + } + return worst + }, 100) // Start with max maintainability, work downwards + + const average = total / allScores.length + const median = getMedian(allScores) + + return { + average, + median, + worst, + } +} diff --git a/src/utils/tree.ts b/src/utils/tree.ts index 4a4f10b..9cb4f8c 100644 --- a/src/utils/tree.ts +++ b/src/utils/tree.ts @@ -1,4 +1,9 @@ -import type { AnalyzedEntity, AnyAnalyzedFile } from '../types' +import type { + AnalyzedEntity, + AnyAnalyzedFile, + FullyAnalyzedEntity, + FullyAnalyzedFile, +} from '../types' export const flattenEntireTree = ( items: AnalyzedEntity[] @@ -14,3 +19,24 @@ export const flattenEntireTree = ( return flattened } + +export const getResultsAsList = ( + analyzedEntities: FullyAnalyzedEntity[], + limit?: number +): FullyAnalyzedFile[] => { + const flatFileResults: FullyAnalyzedFile[] = flattenEntireTree< + FullyAnalyzedFile + >(analyzedEntities) + .filter((entity) => { + return entity.type === 'file' && !!entity.complexityReport + }) + // Sort by codehawk score, ascending (most complex files are first in the list) + .sort((entityA, entityB) => { + return ( + entityA.complexityReport.codehawkScore - + entityB.complexityReport.codehawkScore + ) + }) + + return limit ? flatFileResults.slice(0, limit) : flatFileResults +} From ac13389838b75a21fb5627d090082567691766cd Mon Sep 17 00:00:00 2001 From: sgb-io Date: Sat, 9 Jan 2021 11:49:00 +0000 Subject: [PATCH 3/3] Run prettier --- src/traverseProject.ts | 20 ++-- src/utils/blocklist.ts | 64 +++++------ src/utils/entities.ts | 254 ++++++++++++++++++++--------------------- src/utils/index.ts | 8 +- src/utils/results.ts | 85 +++++++------- src/utils/tree.ts | 84 +++++++------- 6 files changed, 257 insertions(+), 258 deletions(-) diff --git a/src/traverseProject.ts b/src/traverseProject.ts index f05066d..232816a 100644 --- a/src/traverseProject.ts +++ b/src/traverseProject.ts @@ -21,16 +21,18 @@ export const walkSync = ( filename, fullPath: path.join(dir, filename), entity: getFsEntity(path.join(dir, filename)), - relativeDir: slash(dir).replace(slash(process.cwd()), '') - })) - const visibleEntities = parsedEntities.filter((item) => shouldSeeEntity({ - filename: item.filename, - dir, - fullPath: item.fullPath, - entity: item.entity, - options, - relativeDir: item.relativeDir + relativeDir: slash(dir).replace(slash(process.cwd()), ''), })) + const visibleEntities = parsedEntities.filter((item) => + shouldSeeEntity({ + filename: item.filename, + dir, + fullPath: item.fullPath, + entity: item.entity, + options, + relativeDir: item.relativeDir, + }) + ) visibleEntities.forEach((item) => { const { fullPath, filename, entity, relativeDir } = item diff --git a/src/utils/blocklist.ts b/src/utils/blocklist.ts index e15bc73..561a88f 100644 --- a/src/utils/blocklist.ts +++ b/src/utils/blocklist.ts @@ -1,32 +1,32 @@ -import type { AssembledOptions } from '../types' - -export const isBlocklisted = ( - relativeDir: string, - filename: string, - options: AssembledOptions -): boolean => { - const { excludeDirectories, excludeFilenames, excludeExact } = options - - // Check for blocklisted directories - for (let i = 0; i < excludeDirectories.length; i += 1) { - if (relativeDir.startsWith(excludeDirectories[i])) { - return true - } - } - - // Check for blocklisted filename matches - for (let i = 0; i < excludeFilenames.length; i += 1) { - if (filename.includes(excludeFilenames[i])) { - return true - } - } - - // Check for exact matches - for (let i = 0; i < excludeExact.length; i += 1) { - if (excludeExact[i] === `${relativeDir}/${filename}`) { - return true - } - } - - return false -} +import type { AssembledOptions } from '../types' + +export const isBlocklisted = ( + relativeDir: string, + filename: string, + options: AssembledOptions +): boolean => { + const { excludeDirectories, excludeFilenames, excludeExact } = options + + // Check for blocklisted directories + for (let i = 0; i < excludeDirectories.length; i += 1) { + if (relativeDir.startsWith(excludeDirectories[i])) { + return true + } + } + + // Check for blocklisted filename matches + for (let i = 0; i < excludeFilenames.length; i += 1) { + if (filename.includes(excludeFilenames[i])) { + return true + } + } + + // Check for exact matches + for (let i = 0; i < excludeExact.length; i += 1) { + if (excludeExact[i] === `${relativeDir}/${filename}`) { + return true + } + } + + return false +} diff --git a/src/utils/entities.ts b/src/utils/entities.ts index 43ca1bc..81947ae 100644 --- a/src/utils/entities.ts +++ b/src/utils/entities.ts @@ -1,127 +1,127 @@ -import * as fs from 'fs' -import * as path from 'path' -import slash from 'slash' -import isDotfile from 'is-dotfile' -import isDotdir from 'is-dotdir' -import type { AssembledOptions } from '../types' -import { isBlocklisted } from './blocklist' - -const shouldSkipDir = ( - relativeDir: string, - skipDirectories: string[] -): boolean => { - for (let i = 0; i < skipDirectories.length; i += 1) { - if (relativeDir.startsWith(skipDirectories[i])) { - return true - } - } - - return false -} - -const isDotEntity = (fullPath: string): boolean => { - const dotDir = isDotdir(fullPath) || isDotdir(slash(fullPath)) - const dotFile = isDotfile(fullPath) || isDotfile(slash(fullPath)) - return dotDir || dotFile -} - -export const getFsEntity = (fullPath: string): fs.Stats | null => { - let dirent = null - - try { - dirent = fs.statSync(fullPath) - } catch (e) { - // Ingore the error - // Scenarios this catches: symlinks or otherwise inaccessible directories - } - - return dirent -} - -// Should the dir/file show at all in results? -export const shouldSeeEntity = ({ - dir, - entity, - filename, - fullPath, - options, - relativeDir, -}: { - dir: string - entity: fs.Stats - filename: string - fullPath: string - options: AssembledOptions - relativeDir: string -}): boolean => { - // Is a symlink, or other un-readable entity? - if (!entity) { - return false - } - - // Exclude dot directories/files - if (isDotEntity(fullPath)) { - return false - } - - // Is a directory? - if (entity.isDirectory()) { - return true - } - - // Is codehawk config? - if (filename === 'codehawk.json') { - return false - } - - // Should the directory be completely skipped according to user options? - if (shouldSkipDir(relativeDir, options.skipDirectories)) { - return false - } - - return true -} - -// Should the dir/file have complexity analysis run? -export const shouldAnalyzeEntity = ({ - entity, - filename, - fullPath, - options, - relativeDir, -}: { - entity: fs.Stats - filename: string - fullPath: string - options: AssembledOptions - relativeDir: string -}): boolean => { - const extension = path.extname(filename) - - // Is a symlink, or other un-readable entity? - if (!entity) { - return false - } - - // Exclude dot directories/files - if (isDotEntity(fullPath)) { - return false - } - - // Is a directory? - if (entity.isDirectory()) { - return true - } - - // Is the extension included in the options? - if (!options.extensions.includes(extension)) { - return false - } - - // Is the path/filename excluded by user options? - if (isBlocklisted(relativeDir, filename, options)) { - return false - } - - return true -} +import * as fs from 'fs' +import * as path from 'path' +import slash from 'slash' +import isDotfile from 'is-dotfile' +import isDotdir from 'is-dotdir' +import type { AssembledOptions } from '../types' +import { isBlocklisted } from './blocklist' + +const shouldSkipDir = ( + relativeDir: string, + skipDirectories: string[] +): boolean => { + for (let i = 0; i < skipDirectories.length; i += 1) { + if (relativeDir.startsWith(skipDirectories[i])) { + return true + } + } + + return false +} + +const isDotEntity = (fullPath: string): boolean => { + const dotDir = isDotdir(fullPath) || isDotdir(slash(fullPath)) + const dotFile = isDotfile(fullPath) || isDotfile(slash(fullPath)) + return dotDir || dotFile +} + +export const getFsEntity = (fullPath: string): fs.Stats | null => { + let dirent = null + + try { + dirent = fs.statSync(fullPath) + } catch (e) { + // Ingore the error + // Scenarios this catches: symlinks or otherwise inaccessible directories + } + + return dirent +} + +// Should the dir/file show at all in results? +export const shouldSeeEntity = ({ + dir, + entity, + filename, + fullPath, + options, + relativeDir, +}: { + dir: string + entity: fs.Stats + filename: string + fullPath: string + options: AssembledOptions + relativeDir: string +}): boolean => { + // Is a symlink, or other un-readable entity? + if (!entity) { + return false + } + + // Exclude dot directories/files + if (isDotEntity(fullPath)) { + return false + } + + // Is a directory? + if (entity.isDirectory()) { + return true + } + + // Is codehawk config? + if (filename === 'codehawk.json') { + return false + } + + // Should the directory be completely skipped according to user options? + if (shouldSkipDir(relativeDir, options.skipDirectories)) { + return false + } + + return true +} + +// Should the dir/file have complexity analysis run? +export const shouldAnalyzeEntity = ({ + entity, + filename, + fullPath, + options, + relativeDir, +}: { + entity: fs.Stats + filename: string + fullPath: string + options: AssembledOptions + relativeDir: string +}): boolean => { + const extension = path.extname(filename) + + // Is a symlink, or other un-readable entity? + if (!entity) { + return false + } + + // Exclude dot directories/files + if (isDotEntity(fullPath)) { + return false + } + + // Is a directory? + if (entity.isDirectory()) { + return true + } + + // Is the extension included in the options? + if (!options.extensions.includes(extension)) { + return false + } + + // Is the path/filename excluded by user options? + if (isBlocklisted(relativeDir, filename, options)) { + return false + } + + return true +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 4f03a1b..6bec866 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,4 @@ -export * from './blocklist' -export * from './entities' -export * from './results' -export * from './tree' +export * from './blocklist' +export * from './entities' +export * from './results' +export * from './tree' diff --git a/src/utils/results.ts b/src/utils/results.ts index eebb838..e072f00 100644 --- a/src/utils/results.ts +++ b/src/utils/results.ts @@ -1,44 +1,41 @@ -import type { - FullyAnalyzedFile, - ResultsSummary, -} from '../types' - -const getMedian = (numbers: number[]): number => { - const sorted = numbers.slice().sort((a, b) => a - b) - const middle = Math.floor(sorted.length / 2) - - if (sorted.length % 2 === 0) { - return (sorted[middle - 1] + sorted[middle]) / 2 - } - - return sorted[middle] -} - -export const getResultsSummary = ( - resultsAsList: FullyAnalyzedFile[] -): ResultsSummary => { - const allScores: number[] = resultsAsList.reduce((arr: number[], current) => { - if (current.complexityReport) { - arr.push(current.complexityReport.codehawkScore) - } - return arr - }, []) - const total = allScores.reduce((total: number, score) => { - return total + score - }, 0) - const worst = allScores.reduce((worst: number, score) => { - if (score < worst) { - return score - } - return worst - }, 100) // Start with max maintainability, work downwards - - const average = total / allScores.length - const median = getMedian(allScores) - - return { - average, - median, - worst, - } -} +import type { FullyAnalyzedFile, ResultsSummary } from '../types' + +const getMedian = (numbers: number[]): number => { + const sorted = numbers.slice().sort((a, b) => a - b) + const middle = Math.floor(sorted.length / 2) + + if (sorted.length % 2 === 0) { + return (sorted[middle - 1] + sorted[middle]) / 2 + } + + return sorted[middle] +} + +export const getResultsSummary = ( + resultsAsList: FullyAnalyzedFile[] +): ResultsSummary => { + const allScores: number[] = resultsAsList.reduce((arr: number[], current) => { + if (current.complexityReport) { + arr.push(current.complexityReport.codehawkScore) + } + return arr + }, []) + const total = allScores.reduce((total: number, score) => { + return total + score + }, 0) + const worst = allScores.reduce((worst: number, score) => { + if (score < worst) { + return score + } + return worst + }, 100) // Start with max maintainability, work downwards + + const average = total / allScores.length + const median = getMedian(allScores) + + return { + average, + median, + worst, + } +} diff --git a/src/utils/tree.ts b/src/utils/tree.ts index 9cb4f8c..889a5ee 100644 --- a/src/utils/tree.ts +++ b/src/utils/tree.ts @@ -1,42 +1,42 @@ -import type { - AnalyzedEntity, - AnyAnalyzedFile, - FullyAnalyzedEntity, - FullyAnalyzedFile, -} from '../types' - -export const flattenEntireTree = ( - items: AnalyzedEntity[] -): T[] => { - let flattened: T[] = [] - items.forEach((item) => { - if (item.type === 'dir') { - flattened = flattened.concat(flattenEntireTree(item.files)) - } else { - flattened.push(item as T) - } - }) - - return flattened -} - -export const getResultsAsList = ( - analyzedEntities: FullyAnalyzedEntity[], - limit?: number -): FullyAnalyzedFile[] => { - const flatFileResults: FullyAnalyzedFile[] = flattenEntireTree< - FullyAnalyzedFile - >(analyzedEntities) - .filter((entity) => { - return entity.type === 'file' && !!entity.complexityReport - }) - // Sort by codehawk score, ascending (most complex files are first in the list) - .sort((entityA, entityB) => { - return ( - entityA.complexityReport.codehawkScore - - entityB.complexityReport.codehawkScore - ) - }) - - return limit ? flatFileResults.slice(0, limit) : flatFileResults -} +import type { + AnalyzedEntity, + AnyAnalyzedFile, + FullyAnalyzedEntity, + FullyAnalyzedFile, +} from '../types' + +export const flattenEntireTree = ( + items: AnalyzedEntity[] +): T[] => { + let flattened: T[] = [] + items.forEach((item) => { + if (item.type === 'dir') { + flattened = flattened.concat(flattenEntireTree(item.files)) + } else { + flattened.push(item as T) + } + }) + + return flattened +} + +export const getResultsAsList = ( + analyzedEntities: FullyAnalyzedEntity[], + limit?: number +): FullyAnalyzedFile[] => { + const flatFileResults: FullyAnalyzedFile[] = flattenEntireTree< + FullyAnalyzedFile + >(analyzedEntities) + .filter((entity) => { + return entity.type === 'file' && !!entity.complexityReport + }) + // Sort by codehawk score, ascending (most complex files are first in the list) + .sort((entityA, entityB) => { + return ( + entityA.complexityReport.codehawkScore - + entityB.complexityReport.codehawkScore + ) + }) + + return limit ? flatFileResults.slice(0, limit) : flatFileResults +}