Skip to content

Commit

Permalink
Implement 99% of the toolkit (good baseline)
Browse files Browse the repository at this point in the history
  • Loading branch information
illright committed Aug 23, 2024
1 parent 7f8d4d4 commit e407be0
Show file tree
Hide file tree
Showing 16 changed files with 359 additions and 109 deletions.
3 changes: 1 addition & 2 deletions packages/pretty-reporter/src/format-single-diagnostic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { relative } from 'node:path'
import figures from 'figures'
import terminalLink from 'terminal-link'
import chalk from 'chalk'

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

export function formatSingleDiagnostic(d: AugmentedDiagnostic, cwd: string): string {
const x = d.severity === 'error' ? chalk.red(figures.cross) : chalk.yellow(figures.warning)
Expand Down
4 changes: 1 addition & 3 deletions packages/pretty-reporter/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import chalk from 'chalk'
import figures from 'figures'
import type { AugmentedDiagnostic } from '@steiger/types'

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

Expand Down Expand Up @@ -46,5 +46,3 @@ export function formatPretty(diagnostics: Array<AugmentedDiagnostic>, cwd: strin
export function reportPretty(diagnostics: Array<AugmentedDiagnostic>, cwd: string) {
console.error(formatPretty(diagnostics, cwd))
}

export type { AugmentedDiagnostic }
7 changes: 0 additions & 7 deletions packages/pretty-reporter/src/types.ts

This file was deleted.

62 changes: 24 additions & 38 deletions packages/steiger-plugin-fsd/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { enableAllRules, type Config, type ConfigObjectOf, type Plugin } from '@steiger/toolkit'
import { enableAllRules, type ConfigObjectOf, createPlugin, createConfigs } from '@steiger/toolkit'

import ambiguousSliceNames from './ambiguous-slice-names/index.js'
import excessiveSlicing from './excessive-slicing/index.js'
Expand All @@ -17,49 +17,35 @@ import sharedLibGrouping from './shared-lib-grouping/index.js'
import noProcesses from './no-processes/index.js'
import packageJson from '../package.json'

const plugin = {
const rules = [
ambiguousSliceNames,
excessiveSlicing,
forbiddenImports,
inconsistentNaming,
insignificantSlice,
noLayerPublicApi,
noPublicApiSidestep,
noReservedFolderNames,
noSegmentlessSlices,
noSegmentsOnSlicedLayers,
publicApi,
repetitiveNaming,
segmentsByPurpose,
sharedLibGrouping,
noProcesses,
]

const plugin = createPlugin({
meta: {
name: 'steiger-plugin-fsd',
version: packageJson.version,
},
ruleDefinitions: [
ambiguousSliceNames,
excessiveSlicing,
forbiddenImports,
inconsistentNaming,
insignificantSlice,
noLayerPublicApi,
noPublicApiSidestep,
noReservedFolderNames,
noSegmentlessSlices,
noSegmentsOnSlicedLayers,
publicApi,
repetitiveNaming,
segmentsByPurpose,
sharedLibGrouping,
noProcesses,
],
} satisfies Plugin

// export const anotherPlugin = createPlugin({
// meta: {
// name: 'steiger-plugin-hello',
// version: '0.0.0'
// },
// ruleDefinitions: [
// {
// name: 'hello/test',
// check(root, options: { foo: string }) {
// console.log(options.foo)
// return { diagnostics: [] }
// }
// }
// ]
// })
ruleDefinitions: rules,
})

const configs = {
const configs = createConfigs({
recommended: enableAllRules(plugin),
} satisfies Record<string, Config>
})

export default {
plugin,
Expand Down
3 changes: 1 addition & 2 deletions packages/steiger/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { createEffect, sample, combine } from 'effector'
import { debounce, not } from 'patronum'
import type { Rule, Folder, Severity } from '@steiger/types'
import type { AugmentedDiagnostic } from '@steiger/pretty-reporter'
import type { Rule, Folder, Severity, AugmentedDiagnostic } from '@steiger/types'

import { scan, createWatcher } from './features/transfer-fs-to-vfs'
import { defer } from './shared/defer'
Expand Down
8 changes: 4 additions & 4 deletions packages/steiger/src/models/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import z from 'zod'
import { createEvent, createStore } from 'effector'
import { Config, ConfigObject, Plugin, Rule } from '@steiger/types'

export const $config = createStore<ConfigObject | null>(null)
const setConfig = createEvent<ConfigObject>()
export const $config = createStore<ConfigObject<Array<Rule>> | null>(null)
const setConfig = createEvent<ConfigObject<Array<Rule>>>()
$config.on(setConfig, (_state, payload) => payload)

export const $rules = createStore<Array<Rule>>([])
Expand All @@ -30,7 +30,7 @@ function processPlugins(config: Config) {
function mergeConfigObjects(config: Config) {
// TODO: temporary simplified implementation.
// Implement handling the "files" and "ignores" globs in further updates.
return config.reduce((acc: ConfigObject, item) => {
return config.reduce((acc: ConfigObject<Array<Rule>>, item) => {
if ('rules' in item) {
return { ...acc, rules: { ...acc.rules, ...item.rules } }
}
Expand Down Expand Up @@ -77,7 +77,7 @@ export function processConfiguration(config: Config) {
const allRules = processPlugins(config)
const validationScheme = buildValidationScheme(allRules)
const mergedConfig = mergeConfigObjects(config)
const validatedConfig = validationScheme.parse(mergedConfig) as ConfigObject
const validatedConfig = validationScheme.parse(mergedConfig) as ConfigObject<Array<Rule>>

setRules(allRules)
setConfig(validatedConfig)
Expand Down
5 changes: 1 addition & 4 deletions packages/steiger/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { exec } from 'node:child_process'
import { defineConfig } from 'tsup'

export default defineConfig({
Expand All @@ -7,14 +6,12 @@ export default defineConfig({
outExtension: () => ({ js: '.mjs' }),
dts: {
entry: 'src/app.ts',
resolve: true,
},
treeshake: true,
clean: true,
inject: ['./cjs-shim.ts'],
esbuildOptions(options) {
options.define = { 'import.meta.vitest': 'undefined' }
},
onSuccess: async () => {
exec('tsc --emitDeclarationOnly')
},
})
2 changes: 1 addition & 1 deletion packages/toolkit/src/config-object-of.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ConfigObject, Plugin } from '@steiger/types'

/** Accepts a plugin as a type parameter and returns the config object for these rules. */
export type ConfigObjectOf<ThisPlugin> = ThisPlugin extends Plugin<infer Rules> ? ConfigObject<Rules> : never
export type ConfigObjectOf<ThisPlugin> = ThisPlugin extends Plugin<unknown, infer Rules> ? ConfigObject<Rules> : never
19 changes: 19 additions & 0 deletions packages/toolkit/src/create-configs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Config, ConfigObject, OptionsForRule, Plugin, Rule, RuleNames, Severity } from '@steiger/types'

export function enableAllRules<Context, Rules extends Array<Rule>>(
plugin: Plugin<Context, Rules>,
options?: { severity: Exclude<Severity, 'off'> },
): [Plugin<Context, Rules>, ConfigObject<Rules>] {
return [
plugin,
{
rules: Object.fromEntries(plugin.ruleDefinitions.map((rule) => [rule.name, options?.severity ?? 'error'])) as {
[key in RuleNames<Rules>]?: Severity | [Severity, OptionsForRule<Rules, key>]
},
},
]
}

export function createConfigs(configs: Record<string, Config>): Record<string, Config> {
return configs
}
6 changes: 4 additions & 2 deletions packages/toolkit/src/create-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Plugin } from '@steiger/types'
import type { Plugin, Rule } from '@steiger/types'

export function createPlugin<Rules extends string>(plugin: Plugin<Rules>) {
export function createPlugin<const Context, const Rules extends Array<Rule<Context>>>(
plugin: Plugin<Context, Rules>,
): Plugin<Context, Rules> {
return plugin
}
15 changes: 0 additions & 15 deletions packages/toolkit/src/enable-all-rules.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/toolkit/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export type * from '@steiger/types'

export { findAllRecursively } from './find-all-recursively.js'
export { enableAllRules } from './enable-all-rules.js'
export { enableAllRules, createConfigs } from './create-configs.js'
export { createPlugin } from './create-plugin.js'
export type { ConfigObjectOf } from './config-object-of.js'

Expand Down
9 changes: 4 additions & 5 deletions packages/toolkit/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { exec } from 'child_process'
import { defineConfig } from 'tsup'

export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
dts: {
entry: 'src/index.ts',
resolve: true,
},
treeshake: true,
clean: true,
esbuildOptions(options) {
options.define = { 'import.meta.vitest': 'undefined' }
},
onSuccess: async () => {
exec('tsc --emitDeclarationOnly')
},
})
65 changes: 45 additions & 20 deletions packages/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ export interface Folder {

export type BaseRuleOptions = Record<string, unknown>

export interface Rule<Context = void, RuleOptions = BaseRuleOptions, Rules extends string = string> {
export interface Rule<
Context = unknown,
RuleOptions extends BaseRuleOptions = BaseRuleOptions,
RuleName extends string | number | symbol = string,
> {
/** Short code name for the rule. */
name: Rules
check: (this: Context, root: Folder, ruleOptions?: RuleOptions) => RuleResult | Promise<RuleResult>
name: RuleName
check: (this: Context, root: Folder, ruleOptions: RuleOptions) => RuleResult | Promise<RuleResult>
}

export interface RuleResult {
Expand Down Expand Up @@ -57,36 +61,57 @@ export type Fix =
content: string
}

export type Config = Array<ConfigObject | Plugin>
export type Config = Array<ConfigObject<Array<Rule>> | Plugin>

export type Severity = 'off' | 'warn' | 'error'

export interface ConfigObject<Rules extends string = string> {
/**
* Extracts a union of rule names from a given tuple of `Rule` objects.
*
* @example
* type Context = unknown
* type Options = BaseRuleOptions
* type Result = RuleNames<[Rule<Context, Options, 'foo'>, Rule<Context, Options, 'bar'>]>
* // Result is 'foo' | 'bar'
*/
export type RuleNames<Rules extends Array<Rule>> = Rules[number]['name']

type LookUpByName<Union, Name> = Union extends { name: Name } ? Union : never

/**
* Looks up a rule in a tuple of rules by name and then extracts that rule's options type.
*
* @example
* type Context = unknown
* type Rules = [Rule<Context, { foo: string }, 'foo'>, Rule<Context, { bar: number }, 'bar'>]
* type Result = OptionsForRule<Rules, 'foo'>
* // Result is { foo: string }
*/
export type OptionsForRule<Rules extends Array<Rule>, RuleName extends RuleNames<Rules>> =
LookUpByName<Rules[number], RuleName> extends Rule<unknown, infer Options> ? Options : never

export interface ConfigObject<Rules extends Array<Rule>> {
/** Globs of files to check. */
files?: Array<string>
/** Globs of files to ignore. */
ignores?: Array<string>
/** Severity of rules and individual rule options. */
rules?: Partial<Record<Rules, Severity | [Severity, BaseRuleOptions]>>
rules?: { [key in RuleNames<Rules>]?: Severity | [Severity, OptionsForRule<Rules, key>] }
}

export interface Plugin<Rules extends string = string> {
export interface Plugin<Context = unknown, Rules extends Array<Rule<Context>> = Array<Rule>> {
meta: {
name: string
version: string
}
ruleDefinitions: Array<Rule<unknown, BaseRuleOptions, Rules>>
ruleDefinitions: Rules
}

// export interface Plugin<
// Context = unknown,
// Rules extends string = string,
// RuleOptions extends Record<Rules, BaseRuleOptions> = Record<Rules, BaseRuleOptions>,
// > {
// meta: {
// name: string
// version: string
// }
// // CONTINUE: figure out how to type each individual rule's options
// ruleDefinitions: Array<Rule<Context, RuleOptions, Rules>>
// }
// TODO: this type should be owned by `pretty-reporter`, but for some reason
// `tsup` refuses to inline the types from `pretty-reporter`, even though
// it's perfectly capable of inlining types from `types`. Idk, I'll figure it out later.
export interface AugmentedDiagnostic extends Diagnostic {
ruleName: string
severity: Exclude<Severity, 'off'>
getRuleDescriptionUrl(ruleName: string): URL
}
1 change: 1 addition & 0 deletions packages/types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
],
"devDependencies": {
"@steiger/tsconfig": "workspace:*",
"@types/node": "^20.14.2",
"typescript": "^5.5.3"
}
}
Loading

0 comments on commit e407be0

Please sign in to comment.