Skip to content

Commit

Permalink
Redesign the reporter to include links to rules, locations, autofixes
Browse files Browse the repository at this point in the history
  • Loading branch information
illright committed Jul 6, 2024
1 parent d6909c9 commit fe1973e
Show file tree
Hide file tree
Showing 19 changed files with 528 additions and 132 deletions.
6 changes: 6 additions & 0 deletions .changeset/two-houses-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@steiger/pretty-reporter': minor
'steiger': minor
---

Redesign the diagnostic reporter to include links to rules, locations, and auto-fix information
1 change: 1 addition & 0 deletions packages/pretty-reporter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test it with `pnpm run --silent example`. Adjust `example/index.ts` accordingly.
57 changes: 57 additions & 0 deletions packages/pretty-reporter/example/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { reportPretty } from '../src/index.js'

function getRuleDescriptionUrl(ruleName: string) {
return new URL(`https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/${ruleName}`)
}

reportPretty(
[
{
message: 'Inconsistent pluralization of slice names. Prefer all plural names',
ruleName: 'inconsistent-naming',
fixes: [
{
type: 'rename',
path: '/home/user/project/src/entities/user',
newName: 'users',
},
],
location: { path: '/home/user/project/src/entities' },
getRuleDescriptionUrl,
},
{
message: 'This slice has no references. Consider removing it.',
ruleName: 'insignificant-slice',
location: { path: '/home/user/project/src/entities/users' },
getRuleDescriptionUrl,
},
{
message: 'This slice has no references. Consider removing it.',
ruleName: 'insignificant-slice',
location: { path: '/home/user/project/src/entities/user' },
getRuleDescriptionUrl,
},
{
message:
'Having a folder with the name "api" inside a segment could be confusing because that name is commonly used for segments. Consider renaming it.',
ruleName: 'no-reserved-folder-names',
location: { path: '/home/user/project/src/entities/user/ui/api' },
getRuleDescriptionUrl,
},
{
message: 'This slice has no segments. Consider dividing the code inside into segments.',
ruleName: 'no-segmentless-slices',
location: { path: '/home/user/project/src/pages/home' },
getRuleDescriptionUrl,
},
{
message: 'Layer "processes" is deprecated, avoid using it',
ruleName: 'no-processes',
location: { path: '/home/user/project/src/processes' },
getRuleDescriptionUrl,
},
],
'/home/user/project',
)

// reportPretty([], '/home/user/project')
7 changes: 5 additions & 2 deletions packages/pretty-reporter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"description": "A reporter that can print Steiger's diagnostics nice and pretty",
"version": "0.0.0",
"scripts": {
"example": "tsx example/index.ts",
"lint": "eslint .",
"format": "prettier --write . --cache",
"check-formatting": "prettier --check . --cache",
Expand Down Expand Up @@ -34,10 +35,12 @@
"@types/node": "^20.14.2",
"eslint": "^9.4.0",
"prettier": "^3.2.5",
"typescript": "^5.4.5"
"tsx": "^4.16.2",
"typescript": "^5.5.3"
},
"dependencies": {
"chalk": "^5.3.0",
"figures": "^6.1.0"
"figures": "^6.1.0",
"terminal-link": "^3.0.0"
}
}
25 changes: 0 additions & 25 deletions packages/pretty-reporter/src/example.ts

This file was deleted.

37 changes: 37 additions & 0 deletions packages/pretty-reporter/src/format-single-diagnostic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { relative } from 'node:path'
import figures from 'figures'
import terminalLink from 'terminal-link'
import chalk from 'chalk'

import type { AugmentedDiagnostic } from './types.js'

export function formatSingleDiagnostic(d: AugmentedDiagnostic, cwd: string): string {
const x = chalk.red(figures.cross)
const s = chalk.reset(figures.lineDownRight)
const bar = chalk.reset(figures.lineVertical)
const e = chalk.reset(figures.lineUpRight)
const message = chalk.reset(d.message)
const autofixable = d.fixes !== undefined && d.fixes.length > 0 ? chalk.green(`${figures.tick} Auto-fixable`) : null
const location = chalk.gray(formatLocation(d.location, cwd))
const ruleName = chalk.blue(terminalLink(d.ruleName, d.getRuleDescriptionUrl(d.ruleName).toString()))

return `
${s} ${location}
${x} ${message}
${autofixable ? autofixable + `\n${bar}` : bar}
${e} ${ruleName}
`.trim()
}

function formatLocation(location: AugmentedDiagnostic['location'], cwd: string) {
let path = relative(cwd, location.path)
if (location.line !== undefined) {
path += `:${location.line}`

if (location.column !== undefined) {
path += `:${location.column}`
}
}

return path
}
42 changes: 17 additions & 25 deletions packages/pretty-reporter/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,40 @@
import chalk from 'chalk'
import figures from 'figures'

import type { AugmentedDiagnostic } from './types.js'
import { formatSingleDiagnostic } from './format-single-diagnostic.js'
import { s } from './pluralization.js'

export function formatPretty(diagnostics: Array<AugmentedDiagnostic>) {
export function formatPretty(diagnostics: Array<AugmentedDiagnostic>, cwd: string) {
if (diagnostics.length === 0) {
return chalk.green(`${figures.tick} No problems found!`)
}

const footer = chalk.red.bold(`Found ${diagnostics.length} problem${diagnostics.length > 1 ? 's' : ''}`)
let footer = chalk.red.bold(`Found ${diagnostics.length} problem${s(diagnostics.length)}`)

// TODO: enable when we have auto-fixes
// const autofixable = diagnostics.filter((d) => (d.fixes?.length ?? 0) > 0)
// if (autofixable.length === diagnostics.length) {
// footer += ' (all can be fixed automatically)'
// } else if (autofixable.length > 0) {
// footer += ` (${autofixable.length} can be fixed automatically)`
// } else {
// footer += ' (none can be fixed automatically)'
// }
const autofixable = diagnostics.filter((d) => (d.fixes?.length ?? 0) > 0)
if (autofixable.length === diagnostics.length) {
footer += ` (all can be fixed automatically with ${chalk.green.bold('--fix')})`
} else if (autofixable.length > 0) {
footer += ` (${autofixable.length} can be fixed automatically with ${chalk.green.bold('--fix')})`
} else {
footer += ' (none can be fixed automatically)'
}

return (
'\n' +
diagnostics
.map((d) => {
const message = ` ${chalk.red(figures.cross)} ${chalk.reset(d.message)} ${chalk.gray(`// ${d.ruleName}`)}`

// TODO: enable when we have auto-fixes
// if ((d.fixes?.length ?? 0) > 0) {
// message += chalk.green(`\n (${figures.tick} auto-fix available)`)
// }

return message
})
.join('\n\n') +
diagnostics.map((d) => formatSingleDiagnostic(d, cwd)).join('\n\n') +
'\n\n' +
// Due to formatting characters, it won't be exactly the size of the footer, that is okay
chalk.gray(figures.line.repeat(footer.length)) +
'\n ' +
footer +
'\n'
)
}

export function reportPretty(diagnostics: Array<AugmentedDiagnostic>) {
console.error(formatPretty(diagnostics))
export function reportPretty(diagnostics: Array<AugmentedDiagnostic>, cwd: string) {
console.error(formatPretty(diagnostics, cwd))
}

export type { AugmentedDiagnostic }
10 changes: 10 additions & 0 deletions packages/pretty-reporter/src/pluralization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Returns 's' if the amount is not 1.
*
* @example
* `apple${s(1)}` // 'apple'
* `apple${s(2)}` // 'apples'
*/
export function s(amount: number) {
return amount === 1 ? '' : 's'
}
1 change: 1 addition & 0 deletions packages/pretty-reporter/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import type { Diagnostic } from '@steiger/types'

export interface AugmentedDiagnostic extends Diagnostic {
ruleName: string
getRuleDescriptionUrl(ruleName: string): URL
}
2 changes: 1 addition & 1 deletion packages/pretty-reporter/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"extends": "@steiger/tsconfig/base.json",
"include": ["./src", "./reset.d.ts"],
"include": ["./src", "./reset.d.ts", "example/index.ts"],
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/steiger-plugin-fsd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"@types/lodash-es": "^4.17.12",
"@types/pluralize": "^0.0.33",
"tsup": "^8.0.2",
"typescript": "^5.4.5",
"typescript": "^5.5.3",
"vitest": "^1.6.0"
}
}
9 changes: 9 additions & 0 deletions packages/steiger/cjs-shim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Source: https://github.com/evanw/esbuild/issues/1921#issuecomment-1898197331

import { createRequire } from 'node:module'
import path from 'node:path'
import url from 'node:url'

globalThis.require = createRequire(import.meta.url)
globalThis.__filename = url.fileURLToPath(import.meta.url)
globalThis.__dirname = path.dirname(__filename)
2 changes: 1 addition & 1 deletion packages/steiger/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"@total-typescript/ts-reset": "^0.5.1",
"@types/yargs": "^17.0.32",
"tsup": "^8.0.2",
"typescript": "^5.4.5",
"typescript": "^5.5.3",
"vitest": "^1.6.0"
}
}
6 changes: 5 additions & 1 deletion packages/steiger/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import { scan, createWatcher } from './features/transfer-fs-to-vfs'
import { defer } from './shared/defer'
import { $config, type Config } from './models/config'

function getRuleDescriptionUrl(ruleName: string) {
return new URL(`https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/${ruleName}`)
}

const enabledRules = $config.map((config) => {
const ruleConfigs = config?.rules

Expand All @@ -24,7 +28,7 @@ async function runRules({ vfs, rules }: { vfs: Folder; rules: Array<Rule> }) {
const ruleResults = await Promise.all(
rules.map((rule) =>
Promise.resolve(rule.check(vfs, { sourceFileExtension: 'js' })).then(({ diagnostics }) =>
diagnostics.map((d) => ({ ...d, ruleName: rule.name }) as AugmentedDiagnostic),
diagnostics.map<AugmentedDiagnostic>((d) => ({ ...d, ruleName: rule.name, getRuleDescriptionUrl })),
),
),
)
Expand Down
4 changes: 2 additions & 2 deletions packages/steiger/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ if (consoleArgs.watch) {
const [diagnosticsChanged, stopWatching] = await linter.watch(resolve(consoleArgs._[0]))
const unsubscribe = diagnosticsChanged.watch((state) => {
console.clear()
reportPretty(state)
reportPretty(state, process.cwd())
if (consoleArgs.fix) {
applyAutofixes(state)
}
Expand All @@ -75,7 +75,7 @@ if (consoleArgs.watch) {
} else {
const diagnostics = await linter.run(resolve(consoleArgs._[0]))

reportPretty(diagnostics)
reportPretty(diagnostics, process.cwd())
if (consoleArgs.fix) {
applyAutofixes(diagnostics)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/steiger/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"types": ["vitest/importMeta"],
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["./src", "./reset.d.ts", "./tsup.config.ts"]
"include": ["./src", "./reset.d.ts", "./tsup.config.ts", "./cjs-shim.ts"]
}
1 change: 1 addition & 0 deletions packages/steiger/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default defineConfig({
},
treeshake: true,
clean: true,
inject: ['./cjs-shim.ts'],
esbuildOptions(options) {
options.define = { 'import.meta.vitest': 'undefined' }
},
Expand Down
2 changes: 1 addition & 1 deletion packages/types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@
],
"devDependencies": {
"@steiger/tsconfig": "workspace:*",
"typescript": "^5.4.5"
"typescript": "^5.5.3"
}
}
Loading

0 comments on commit fe1973e

Please sign in to comment.