diff --git a/node/package.json b/node/package.json index 909b0e3..f082d78 100644 --- a/node/package.json +++ b/node/package.json @@ -1,47 +1,47 @@ { - "name": "multiflag", - "version": "2.0.0", - "description": "flag/bitflag helper", - "main": "index.js", - "scripts": { - "build": "tsup", - "postbuild": "cp package.json README.md ../LICENSE dist", - "test": "vitest --ui", - "coverage": "vitest run --coverage", - "type-check": "tsc --noEmit", - "format": "prettier --write src/ tests/" - }, - "tsup": { - "entry": [ - "src/index.ts" + "name": "multiflag", + "version": "2.0.0", + "description": "flag/bitflag helper", + "main": "index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/louisdevie/multiflag.git" + }, + "keywords": [ + "flag", + "bitflag" ], - "splitting": false, - "dts": true, - "minify": true, - "sourcemap": true, - "clean": true - }, - "repository": { - "type": "git", - "url": "git+https://github.com/louisdevie/multiflag.git" - }, - "keywords": [ - "flag", - "bitflag" - ], - "author": "Louis DEVIE", - "license": "MIT", - "bugs": { - "url": "https://github.com/louisdevie/multiflag/issues" - }, - "homepage": "https://github.com/louisdevie/multiflag#readme", - "devDependencies": { - "@vitest/coverage-v8": "^4.0.18", - "@vitest/ui": "^4.0.18", - "prettier": "^3.8.1", - "ts-node": "^10.9.2", - "tsup": "^8.5.1", - "typescript": "^5.9.3", - "vitest": "^4.0.18" - } + "author": "Louis DEVIE", + "license": "MIT", + "bugs": { + "url": "https://github.com/louisdevie/tatsuki/issues" + }, + "homepage": "https://github.com/louisdevie/multiflag#readme", + "scripts": { + "build": "tsup", + "postbuild": "cp package.json README.md ../LICENSE dist", + "test": "vitest --ui", + "coverage": "vitest run --coverage", + "type-check": "tsc --noEmit", + "format": "prettier --write src/ tests/" + }, + "tsup": { + "entry": [ + "src/index.ts" + ], + "splitting": false, + "dts": true, + "minify": true, + "sourcemap": true, + "clean": true + }, + "devDependencies": { + "@vitest/coverage-v8": "^4.0.18", + "@vitest/ui": "^4.0.18", + "prettier": "^3.8.1", + "ts-node": "^10.9.2", + "tsup": "^8.5.1", + "typescript": "^5.9.3", + "vitest": "^4.0.18" + } } diff --git a/node/src/builders/declarative.ts b/node/src/builders/declarative.ts index 10a24c0..5c31622 100644 --- a/node/src/builders/declarative.ts +++ b/node/src/builders/declarative.ts @@ -1,8 +1,5 @@ -import type { - DefineWithOrdinal, - DefineWithValue, - DefineWithValueOrOrdinal, -} from './syntax' +import type { DefineWithOrdinal, DefineWithValue, DefineWithValueOrOrdinal } from './syntax' +import { FlagsGraph, refByAlias } from '~/definitions' // Generic helper functions @@ -10,9 +7,7 @@ import type { * Copies the record keys into the 'as' property of the values and return an * array containing those values. */ -function toDefinitionArray( - record: Record, -): (D & { as: string })[] { +function toDeclarationArray(record: Record): (D & { as: string })[] { return Object.keys(record).map((key) => ({ ...record[key], as: key })) } @@ -22,35 +17,9 @@ export type FlagWithValue = | { value: F; as?: string; requires?: string[] } | { compose: string[]; as: string } -export type NamedFlagWithValue = - | { value: F; requires?: string[] } - | { compose: string[] } +export type NamedFlagWithValue = { value: F; requires?: string[] } | { compose: string[] } -export type ListOfFlagsWithValue = - | FlagWithValue[] - | Record> - -export function applyDeclarationsWithValue( - declarations: ListOfFlagsWithValue, - builder: DefineWithValue, -) { - const declarationsArray = Array.isArray(declarations) - ? declarations - : toDefinitionArray(declarations) - - for (const declaration of declarationsArray) { - if ('compose' in declaration) { - builder.define(declaration.as).compose(...declaration.compose) - } else { - // it doesn't matter if "as" is undefined, - // we can always call withValue() on the returned value - builder - .define(declaration.as!) - .withValue(declaration.value) - .requires(...(declaration.requires ?? [])) - } - } -} +export type ListOfFlagsWithValue = FlagWithValue[] | Record> // Declarations for builders that supports only definitions by ordinal @@ -66,28 +35,6 @@ export type ListOfFlagsWithOrdinal = | FlagWithOrdinal[] | Record> -export function applyDeclarationsWithOrdinal( - declarations: ListOfFlagsWithOrdinal, - builder: DefineWithOrdinal, -) { - const declarationsArray = Array.isArray(declarations) - ? declarations - : toDefinitionArray(declarations) - - for (const declaration of declarationsArray) { - if ('compose' in declaration) { - builder.define(declaration.as).compose(...declaration.compose) - } else { - // it doesn't matter if "as" is undefined, - // we can always call withOrdinal() on the returned value - builder - .define(declaration.as!) - .withOrdinal(declaration.ordinal) - .requires(...(declaration.requires ?? [])) - } - } -} - // Declarations for builders that supports definitions by value and ordinal export type FlagWithValueOrOrdinal = @@ -104,13 +51,13 @@ export type ListOfFlagsWithValueOrOrdinal = | FlagWithValueOrOrdinal[] | Record> -export function applyDeclarationsWithValueOrOrdinal( +export function applyDeclarations( declarations: ListOfFlagsWithValueOrOrdinal, builder: DefineWithValueOrOrdinal, ) { const declarationsArray = Array.isArray(declarations) ? declarations - : toDefinitionArray(declarations) + : toDeclarationArray(declarations) for (const declaration of declarationsArray) { if ('compose' in declaration) { diff --git a/node/src/builders/generic.ts b/node/src/builders/generic.ts index 09e6739..1f8391f 100644 --- a/node/src/builders/generic.ts +++ b/node/src/builders/generic.ts @@ -1,94 +1,43 @@ -import { InvalidOperationError, InvalidReferenceError } from '../errors' -import { FlagDefinition, FlagsDictionary } from '~' -import { printFlagValue } from '../definitions' -import { GenericFlagsDictionary } from '../definitions/dictionary' +import { InvalidOperationError } from '~/errors' +import { FlagsGraph, PartialFlagDefinition, PartialFlagInit, refByAlias } from '~/definitions' -export interface FlagDefinitionFactory { - readonly supportsDefinitionsByValue: boolean - readonly supportsDefinitionsByOrdinal: boolean +export class FlagSetBuilder { + private readonly _graph: FlagsGraph + private _currentDefinition: PartialFlagInit | undefined - precomputeTopDown(partialDefinition: PartialDefinition): void - - precomputeBottomUp(partialDefinition: PartialDefinition): void - - createFlagDefinition(partialDefinition: PartialDefinition): FlagDefinition -} - -export interface PartialDefinition { - alias?: string - value?: F - parentRefs?: Set - parents?: Set> - children?: Set> - baseValues?: S - additiveValues?: S - subtractiveValues?: S -} - -function addRelationship( - parent: PartialDefinition, - child: PartialDefinition, -): void { - if (parent.children === undefined) { - parent.children = new Set([child]) - } else { - parent.children.add(child) - } - if (child.parents === undefined) { - child.parents = new Set([parent]) - } else { - child.parents.add(parent) - } -} - -export class GenericFlagSetBuilder { - private readonly _factory: FlagDefinitionFactory - private readonly _definitions: Set> - private _currentDefinition: PartialDefinition | undefined - - public constructor(factory: FlagDefinitionFactory) { - this._factory = factory - this._definitions = new Set() + public constructor() { + this._graph = new FlagsGraph() this._currentDefinition = undefined } - private get canMoveOnToNextDefinition(): boolean { - return ( - this._currentDefinition === undefined || - this._currentDefinition.value !== undefined || - this._currentDefinition.parentRefs !== undefined - ) - } - - private createNewDefinition(alias: string | undefined): void { - const def = { alias } - this._definitions.add(def) - this._currentDefinition = def - } - public define(alias: string | undefined): void { - if (!this.canMoveOnToNextDefinition) { + if ( + this._currentDefinition !== undefined && + this._currentDefinition.value === undefined && + this._currentDefinition.parents === undefined + ) { throw new InvalidOperationError('define') } - this.createNewDefinition(alias) + this.finishCurrentDefinition() + this._currentDefinition = { alias } } public compose(flags: string[]): void { if ( this._currentDefinition === undefined || this._currentDefinition.alias === undefined || - this._currentDefinition.parentRefs !== undefined + this._currentDefinition.parents !== undefined ) { throw new InvalidOperationError('compose') } - this._currentDefinition.parentRefs = new Set(flags) + this._currentDefinition.parents = refByAlias([...new Set(flags)]) } public withValue(value: F): void { if ( this._currentDefinition === undefined || this._currentDefinition.value !== undefined || - this._currentDefinition.parentRefs !== undefined + this._currentDefinition.parents !== undefined ) { throw new InvalidOperationError('withValue') } @@ -99,14 +48,25 @@ export class GenericFlagSetBuilder { if ( this._currentDefinition === undefined || this._currentDefinition.value === undefined || - this._currentDefinition.parentRefs !== undefined + this._currentDefinition.parents !== undefined ) { throw new InvalidOperationError('requires') } - this._currentDefinition.parentRefs = new Set(flags) + this._currentDefinition.parents = refByAlias([...new Set(flags)]) } - public buildDictionary(): FlagsDictionary { + private finishCurrentDefinition() { + if (this._currentDefinition !== undefined) { + this._graph.put(this._currentDefinition) + } + this._currentDefinition = undefined + } + + public finish(): FlagsGraph { + this.finishCurrentDefinition() + return this._graph + } + /* // this array will contain the nodes of the graph, in topological order const sorted: PartialDefinition[] = [] @@ -157,15 +117,14 @@ export class GenericFlagSetBuilder { this._factory.precomputeBottomUp(sorted[i]) dict.add(this._factory.createFlagDefinition(sorted[i])) } - return dict - } + return dict*/ - private displayPartialDefinition(definition: PartialDefinition): string { + /*private displayPartialDefinition(definition: PartialDefinition): string { if (definition.alias) { return '"' + definition.alias + '"' } else { // generate the definition and try to print its base value return printFlagValue(this._factory.createFlagDefinition(definition)) } - } + }*/ } diff --git a/node/src/builders/number.ts b/node/src/builders/number.ts index 80687d5..a1e6049 100644 --- a/node/src/builders/number.ts +++ b/node/src/builders/number.ts @@ -1,4 +1,3 @@ -import { BitFlagSet, FlagDefinition, FlagsDictionary } from '~' import { DefineWithValueOrOrdinal, RequireParentsOrDefineWithValueOrOrdinal, @@ -6,23 +5,24 @@ import { WithValueOrOrdinalOrCompose, } from './syntax' import { - applyDeclarationsWithValueOrOrdinal, + applyDeclarations, FlagWithValueOrOrdinal, ListOfFlagsWithValueOrOrdinal, NamedFlagWithValueOrOrdinal, } from './declarative' -import { BitFlagDefinition } from '../definitions' -import { FlagDefinitionFactory, GenericFlagSetBuilder, PartialDefinition } from './generic' +import { BitFlagDefinitionFactory, FlagDefinition } from '~/definitions' +import { FlagSetBuilder } from './generic' +import { BitFlagSet } from '~/flagsets' export class BitFlagSetBuilder implements WithValueOrOrdinalOrCompose, RequireParentsOrDefineWithValueOrOrdinal { - private readonly _underlying: GenericFlagSetBuilder + private readonly _underlying: FlagSetBuilder public constructor() { - this._underlying = new GenericFlagSetBuilder(new BitFlagDefinitionFactory()) + this._underlying = new FlagSetBuilder() } public define(): WithValueOrOrdinal @@ -55,60 +55,11 @@ export class BitFlagSetBuilder return this } - public getDictionary(): FlagsDictionary { - return this._underlying.buildDictionary() as any - } - public getResult(): BitFlagSet { - return new BitFlagSet(this.getDictionary()) - } -} - -class BitFlagDefinitionFactory implements FlagDefinitionFactory { - public readonly supportsDefinitionsByOrdinal = true - public readonly supportsDefinitionsByValue = true - - public precomputeTopDown(partialDefinition: PartialDefinition): void { - partialDefinition.additiveValues = 0 - partialDefinition.baseValues = 0 - if (partialDefinition.value !== undefined) { - partialDefinition.additiveValues = partialDefinition.value - partialDefinition.baseValues = partialDefinition.value - } - if (partialDefinition.parents !== undefined) { - for (const parent of partialDefinition.parents.values()) { - partialDefinition.additiveValues = - partialDefinition.additiveValues | (parent.additiveValues ?? 0) - if (partialDefinition.value === undefined) { - partialDefinition.baseValues = - partialDefinition.baseValues | (parent.baseValues ?? 0) - } - } - } - } - - public precomputeBottomUp(partialDefinition: PartialDefinition): void { - partialDefinition.subtractiveValues = 0 - if (partialDefinition.value) { - partialDefinition.subtractiveValues = partialDefinition.value - } - if (partialDefinition.children) { - for (const child of partialDefinition.children.values()) { - partialDefinition.subtractiveValues = - partialDefinition.subtractiveValues | (child.subtractiveValues ?? 0) - } - } - } - - public createFlagDefinition( - partialDefinition: PartialDefinition, - ): FlagDefinition { - return new BitFlagDefinition( - partialDefinition.baseValues ?? 0, - partialDefinition.additiveValues ?? 0, - partialDefinition.subtractiveValues ?? 0, - partialDefinition.alias, - ) + const graph = this._underlying.finish() + const definitions = graph.sortedDefinitions() + const factory = new BitFlagDefinitionFactory() + return new BitFlagSet(graph.intoDictionary(factory)) } } @@ -118,6 +69,6 @@ export function createBitFlagSet( ): BitFlagSet & Record> export function createBitFlagSet(declarations: ListOfFlagsWithValueOrOrdinal): BitFlagSet { const builder = new BitFlagSetBuilder() - applyDeclarationsWithValueOrOrdinal(declarations, builder) + applyDeclarations(declarations, builder) return builder.getResult() } diff --git a/node/src/builders/syntax.ts b/node/src/builders/syntax.ts index 75a766f..0a207f6 100644 --- a/node/src/builders/syntax.ts +++ b/node/src/builders/syntax.ts @@ -1,11 +1,6 @@ import type { FlagsDictionary, FlagSet } from '~' export interface Root> { - /** - * Build a dictionary with the flags previously defined. - */ - getDictionary(): FlagsDictionary - /** * Build a flag set with the flags previously defined. */ @@ -14,8 +9,7 @@ export interface Root> { // Syntax for builders that supports only definitions by value -export interface DefineWithValue> - extends Root { +export interface DefineWithValue> extends Root { /** * Define an anonymous flag. */ @@ -44,8 +38,7 @@ export interface WithValueOrCompose extends WithValue { compose(...flags: string[]): DefineWithValue } -export interface RequireParentsOrDefineWithValue - extends DefineWithValue { +export interface RequireParentsOrDefineWithValue extends DefineWithValue { /** * Set the parents of this flag. * @param flags The names of the parent flags. @@ -55,8 +48,7 @@ export interface RequireParentsOrDefineWithValue // Syntax for builders that supports only definitions by ordinal -export interface DefineWithOrdinal> - extends Root { +export interface DefineWithOrdinal> extends Root { /** * Define an anonymous flag. */ @@ -86,8 +78,7 @@ export interface WithOrdinalOrCompose extends WithOrdinal { compose(...flags: string[]): DefineWithOrdinal } -export interface RequireParentsOrDefineWithOrdinal - extends DefineWithOrdinal { +export interface RequireParentsOrDefineWithOrdinal extends DefineWithOrdinal { /** * Set the parents of this flag. * @param flags The names of the parent flags. @@ -97,8 +88,7 @@ export interface RequireParentsOrDefineWithOrdinal // Syntax for builders that supports definitions by value and ordinal -export interface DefineWithValueOrOrdinal> - extends Root { +export interface DefineWithValueOrOrdinal> extends Root { /** * Define an anonymous flag. */ @@ -123,13 +113,10 @@ export interface WithValueOrOrdinal { * @param ordinal The number of the flag (starting at 1). A unique value * will be assigned based on this number. */ - withOrdinal( - ordinal: number, - ): RequireParentsOrDefineWithValueOrOrdinal + withOrdinal(ordinal: number): RequireParentsOrDefineWithValueOrOrdinal } -export interface WithValueOrOrdinalOrCompose - extends WithValueOrOrdinal { +export interface WithValueOrOrdinalOrCompose extends WithValueOrOrdinal { /** * Define this flag as a composed flag. * @param flags The name of the flags in the group. @@ -137,8 +124,11 @@ export interface WithValueOrOrdinalOrCompose compose(...flags: string[]): DefineWithValueOrOrdinal } -export interface RequireParentsOrDefineWithValueOrOrdinal - extends DefineWithValueOrOrdinal { +export interface RequireParentsOrDefineWithValueOrOrdinal extends DefineWithValueOrOrdinal< + F, + S, + R +> { /** * Set the parents of this flag. * @param flags The names of the parent flags. diff --git a/node/src/definitions/dictionary.ts b/node/src/definitions/dictionary.ts index a15f75a..d6c3834 100644 --- a/node/src/definitions/dictionary.ts +++ b/node/src/definitions/dictionary.ts @@ -1,67 +1,48 @@ -import type { FlagDefinition } from '.' -import { ReusedFlagAliasError, ReusedFlagValueError } from '../errors' - -/** - * A collection of {@link FlagDefinition}s. - */ export interface FlagsDictionary { - /** - * Search for a flag in the collection. - * @param alias The alias of the flag. - * @returns The corresponding definition, or `undefined` if there is no flag - * with this alias. - */ findByAlias(alias: string): FlagDefinition | undefined + + findByValue(value: F): FlagDefinition | undefined } -/** - * Built-in dictionary implementation. - * @internal - */ -export class GenericFlagsDictionary implements FlagsDictionary { - private readonly _named: Map> - private readonly _anonymous: FlagDefinition[] - - public constructor() { - this._named = new Map() - this._anonymous = [] - } - - public add(definition: FlagDefinition): void { - if (definition.alias === undefined) { - this._anonymous.push(definition) - } else { - this._named.set(definition.alias, definition) - } - } +export interface FlagDefinition { + /** + * The alias of the flag. + */ + readonly alias: string | undefined /** - * Search for a flag in the collection. - * @param alias The alias of the flag. - * @returns The corresponding definition, or `undefined` if there is no flag - * with this alias. + * A set containing the value of the flag, or multiple values if it is a composed flag. */ - public findByAlias(alias: string): FlagDefinition | undefined { - return this._named.get(alias) - } + readonly values: S + + /** + * Test if this flag and all its parents are present in the set. + * @param set A set of flags. + * @returns `true` if the set includes this flags and its parents. + */ + isIn(set: S): boolean + + /** + * Add this flag and all its parents to the set. + * @param set A set of flags. + * @returns A new set of flags containing the flags from the `set`, this flag and its parents. + */ + addTo(set: S): S + + /** + * Removes this flag and all its children from the set. + * @param set A set of flags. + * @returns A new set of flags containing the flags from the `set` except this flag and its children. + */ + removeFrom(set: S): S } -/* - public define(definition: FlagDefinition) { - for (const other of this._named.values()) { - if ( - definition.alias !== undefined && - definition.alias === other.alias - ) { - throw new ReusedFlagAliasError(definition.alias) - } - if (definition.hasSameValue(other)) { - throw new ReusedFlagValueError(definition, other) - } - } - for (const other of this._anonymous) { - if (definition.hasSameValue(other)) { - throw new ReusedFlagValueError(definition, other) - } - } - */ +export const valueToString = Symbol() + +export function printFlagValue(flag: FlagDefinition): string { + if (valueToString in flag) { + return (flag[valueToString] as Function)() + } else { + return String(flag.values) + } +} diff --git a/node/src/definitions/graph.ts b/node/src/definitions/graph.ts new file mode 100644 index 0000000..187fa32 --- /dev/null +++ b/node/src/definitions/graph.ts @@ -0,0 +1,258 @@ +import { FlagDefinition, FlagsDictionary, printFlagValue } from '~/definitions' +import { InternalError } from '~/errors' + +export interface PartialFlagInit { + alias?: string + value?: F + parents?: PartialFlagInit[] +} + +export function refByAlias(refs: string[]): PartialFlagInit[] { + return refs.map((ref) => ({ alias: ref })) +} + +export interface FlagDefinitionFactory { + makeDefinitions( + sortedPartialDefinitions: PartialFlagDefinition[], + results: Map, FlagDefinition>, + ): void +} + +/** + * A graph with {@link PartialFlagDefinition} vertices. + */ +export class FlagsGraph { + private readonly _definitions: PartialFlagDefinition[] + private readonly _aliasToDefinition: Map> + private readonly _valueToDefinition: Map> + + public constructor() { + this._definitions = [] + this._aliasToDefinition = new Map() + this._valueToDefinition = new Map() + } + + private add(pfd: PartialFlagDefinition): void { + this._definitions.push(pfd) + if (pfd.alias !== undefined) { + this._aliasToDefinition.set(pfd.alias, pfd) + } + if (pfd.value !== undefined) { + this._valueToDefinition.set(pfd.value, pfd) + } + } + + /** + * Search for a flag matching the alias or value of `init`. If no flag is found, a new + * placeholder flag is created to make a forward reference. + */ + private findOrAddPlaceholder(init: PartialFlagInit): PartialFlagDefinition { + let pfd + if (init.alias !== undefined) { + pfd = this._aliasToDefinition.get(init.alias) + } + + if (pfd === undefined) { + pfd = new PartialFlagDefinition(true, init) + this.add(pfd) + } + return pfd + } + + /** + * Add a new flag to the graph, or upgrade the existing definition. + */ + public put(init: PartialFlagInit): PartialFlagDefinition { + let pfd + if (init.alias !== undefined) { + pfd = this._aliasToDefinition.get(init.alias) + } + + if (pfd === undefined) { + pfd = new PartialFlagDefinition(false, init) + this.add(pfd) + } else { + pfd.upgrade(init) + } + + if (init.parents !== undefined) { + for (const parentInit of init.parents) { + const parentPfd = this.findOrAddPlaceholder(parentInit) + pfd.addParent(parentPfd) + } + } + + return pfd + } + + /** + * A list of all definitions that are not placeholders. + */ + public get definitions(): Iterable> { + return this._definitions.filter((d) => !d.isPlaceholder) + } + + /** + * Return an array containing the definitions in topological order. + * @throws CircularReferenceError when the graph can not be sorted because it contains a cycle. + */ + private sortedDefinitions(): PartialFlagDefinition[] { + // work on a copy of the graph + const remaining = new Map(this._definitions.map((pfd) => [pfd, new Set(pfd.parents)])) + + const sorted: PartialFlagDefinition[] = [] + const start: PartialFlagDefinition[] = [] + + for (const pfd of [...remaining.keys()]) { + const parents = remaining.get(pfd)! + if (parents.size === 0) { + start.push(pfd) + remaining.delete(pfd) + } + } + + while (start.length > 0) { + const current = start.pop()! + sorted.push(current) + for (const child of current.children) { + const parents = remaining.get(child)! + parents.delete(current) + if (parents.size === 0) { + start.push(child) + remaining.delete(child) + } + } + } + + return sorted + } + + public intoDictionary(factory: FlagDefinitionFactory): FlagsDictionary { + const sortedPartialDefinitions = this.sortedDefinitions() + + const definitions = new Map, FlagDefinition>() + factory.makeDefinitions(sortedPartialDefinitions, definitions) + + const aliasToDefinition = new Map>() + for (const [alias, pfd] of this._aliasToDefinition.entries()) { + const definition = definitions.get(pfd) + if (definition === undefined) { + throw new InternalError(`factory didn't provide any definition for ${pfd}`) + } + aliasToDefinition.set(alias, definition) + } + + const valueToDefinition = new Map>() + for (const [value, pfd] of this._valueToDefinition.entries()) { + const definition = definitions.get(pfd) + if (definition === undefined) { + throw new InternalError(`factory didn't provide any definition for ${pfd}`) + } + valueToDefinition.set(value, definition) + } + + return new BiMapFlagsDictionary(aliasToDefinition, valueToDefinition) + } +} + +class BiMapFlagsDictionary implements FlagsDictionary { + private readonly _aliasToDefinition: Map> + private readonly _valueToDefinition: Map> + + public constructor( + aliasToDefinition: Map>, + valueToDefinition: Map>, + ) { + this._aliasToDefinition = aliasToDefinition + this._valueToDefinition = valueToDefinition + } + + public findByAlias(alias: string): FlagDefinition | undefined { + return this._aliasToDefinition.get(alias) + } + + public findByValue(value: F): FlagDefinition | undefined { + return this._valueToDefinition.get(value) + } +} + +/** + * A partial {@link FlagDefinition} used for building a flag graph. + */ +export class PartialFlagDefinition { + private readonly _alias: string | undefined + private readonly _parents: Set> + private readonly _children: Set> + + private _isPlaceholder: boolean + private _value: F | undefined + + public constructor(isPlaceholder: boolean, init: PartialFlagInit) { + this._isPlaceholder = isPlaceholder + this._alias = init.alias + this._value = init.value + this._parents = new Set() + this._children = new Set() + } + + /** + * The alias of the flag, or `undefined` if no alias has been set yet. + */ + public get alias(): string | undefined { + return this._alias + } + + /** + * The value of the flag, or `undefined` if no value has been set yet. + */ + public get value(): F | undefined { + return this._value + } + + /** + * Indicates that this flag has not been defined yet, but has been referenced by another flag. + */ + public get isPlaceholder(): boolean { + return this._isPlaceholder + } + + /** + * Upgrade a placeholder flag to an actual definition. + */ + public upgrade(init: PartialFlagInit): void { + this._value = init.value + this._isPlaceholder = false + } + + /** + * The list of all parent flags (including forward references). + */ + public get parents(): Iterable> { + return this._parents + } + + /** + * The list of all children flags. + */ + public get children(): Iterable> { + return this._children + } + + /** + * Creates a parent-child relationship between this flag and another. + */ + public addParent(parentPfd: PartialFlagDefinition): void { + this._parents.add(parentPfd) + parentPfd._children.add(this) + } + + public toString(): string { + if (this._alias !== undefined) { + return '"' + this._alias + '"' + } else if (this._value !== undefined) { + return JSON.stringify(this._value) + } else { + return '[?]' + } + } +} diff --git a/node/src/definitions/index.ts b/node/src/definitions/index.ts index 5906b22..8021a7e 100644 --- a/node/src/definitions/index.ts +++ b/node/src/definitions/index.ts @@ -1,23 +1,9 @@ -export { FlagsDictionary } from './dictionary' -export { BitFlagDefinition } from './number' - -export interface FlagDefinition { - /** - * The alias of the flag. - */ - readonly alias: string | undefined - - readonly values: S - - hasSameValue(other: FlagDefinition): boolean -} - -export const valueToString = Symbol() - -export function printFlagValue(flag: FlagDefinition): string { - if (valueToString in flag) { - return (flag[valueToString] as Function)() - } else { - return String(flag.values) - } -} +export { FlagsDictionary, FlagDefinition, printFlagValue, valueToString } from './dictionary' +export { + FlagsGraph, + PartialFlagInit, + PartialFlagDefinition, + refByAlias, + FlagDefinitionFactory, +} from './graph' +export { BitFlagDefinition, BitFlagDefinitionFactory } from './number' diff --git a/node/src/definitions/number.ts b/node/src/definitions/number.ts index 9169908..bdbe98e 100644 --- a/node/src/definitions/number.ts +++ b/node/src/definitions/number.ts @@ -1,20 +1,21 @@ -import { FlagDefinition, valueToString } from '.' +import { FlagDefinition, FlagDefinitionFactory, PartialFlagDefinition } from '.' + +interface PrecomputedValues { + base: number + additive: number + subtractive: number +} export class BitFlagDefinition implements FlagDefinition { - private readonly _baseValues: number - private readonly _additiveValues: number - private readonly _subtractiveValues: number + private readonly _baseValue: number + private readonly _additiveValue: number + private readonly _subtractiveValue: number private readonly _alias: string | undefined - public constructor( - baseValues: number, - additiveValues: number, - subtractiveValues: number, - alias: string | undefined, - ) { - this._baseValues = baseValues - this._additiveValues = additiveValues - this._subtractiveValues = subtractiveValues + public constructor(precomputedValues: PrecomputedValues, alias: string | undefined) { + this._baseValue = precomputedValues.base + this._additiveValue = precomputedValues.additive + this._subtractiveValue = precomputedValues.subtractive this._alias = alias } @@ -23,10 +24,63 @@ export class BitFlagDefinition implements FlagDefinition { } public get values(): number { - return this._baseValues + return this._baseValue } - public hasSameValue(other: FlagDefinition): boolean { - return other instanceof BitFlagDefinition && this._baseValues === other._baseValues + public isIn(set: number): boolean { + return (set & this._additiveValue) === this._additiveValue + } + + public addTo(set: number): number { + return set | this._additiveValue + } + + public removeFrom(set: number): number { + return set | this._additiveValue + } +} + +export class BitFlagDefinitionFactory implements FlagDefinitionFactory { + public makeDefinitions( + sortedPartialDefinitions: PartialFlagDefinition[], + results: Map, FlagDefinition>, + ): void { + const precomputedValues = new Map, PrecomputedValues>() + + for (let i = 0; i < sortedPartialDefinitions.length; i++) { + const pfd = sortedPartialDefinitions[i] + + const values: PrecomputedValues = { + base: pfd.value ?? 0, + additive: pfd.value ?? 0, + subtractive: pfd.value ?? 0, + } + + for (const parentPfd of pfd.parents) { + const parentValues = precomputedValues.get(parentPfd) + if (parentValues !== undefined) { + values.additive = values.additive | parentValues.additive + if (pfd.value === undefined) { + values.base = values.base | parentValues.base + } + } + } + + precomputedValues.set(pfd, values) + } + + for (let i = sortedPartialDefinitions.length - 1; i >= 0; i--) { + const pfd = sortedPartialDefinitions[i] + const values = precomputedValues.get(pfd)! + + for (const childPfd of pfd.children) { + const childValues = precomputedValues.get(childPfd) + if (childValues !== undefined) { + values.subtractive = values.subtractive | childValues.subtractive + } + } + + results.set(pfd, new BitFlagDefinition(values, pfd.alias)) + } } } diff --git a/node/src/errors.ts b/node/src/errors.ts index 21717fd..4895df4 100644 --- a/node/src/errors.ts +++ b/node/src/errors.ts @@ -1,4 +1,5 @@ import { FlagDefinition, printFlagValue } from './definitions' +import pkg from '../package.json' /** * Error thrown when a feature is not available in the current environment. @@ -38,10 +39,7 @@ export class ReusedFlagAliasError extends Error { */ export class ReusedFlagValueError extends Error { /** @internal */ - public constructor( - a: FlagDefinition, - b: FlagDefinition, - ) { + public constructor(a: FlagDefinition, b: FlagDefinition) { super( `The value ${printFlagValue(a)} is already being used ${b.alias ? 'for the flag "' + b.alias + '"' : 'by another flag'}.`, ) @@ -54,18 +52,33 @@ export class ReusedFlagValueError extends Error { export class InvalidOperationError extends Error { /** @internal */ public constructor(methodName: string, explanation?: string) { - super( - `.${methodName}() is not allowed here${explanation ? ': ' + explanation : ''}.`, - ) + super(`.${methodName}() is not allowed here${explanation ? ': ' + explanation : ''}.`) } } /** - * Error thrown by builders when referencing a flag that is not defined. + * Error thrown by builders when a flag references a flag that is not defined. */ -export class InvalidReferenceError extends Error { +export class UndefinedReferenceError extends Error { /** @internal */ public constructor(alias: string, requiredBy: string) { super(`Undefined flag "${alias}" required by ${requiredBy}.`) } } + +/** + * Error thrown by builders when a circular dependency is created. + */ +export class CircularReferenceError extends Error { + /** @internal */ + public constructor(chain: string[]) { + super(`Circular reference found: ${chain.join(' → ')}.`) + } +} + +export class InternalError extends Error { + /** @internal */ + public constructor(message: string) { + super(`[Tatsuki internal error] ${message}. Please report it at ${pkg.bugs.url}.`) + } +} diff --git a/node/src/flagsets/base64.ts b/node/src/flagsets/base64.ts index 30a441c..3b351fe 100644 --- a/node/src/flagsets/base64.ts +++ b/node/src/flagsets/base64.ts @@ -1,15 +1,6 @@ import type { FlagSet } from '.' -import { - decodeB64Byte, - encodeB64Byte, - normaliseB64String, - ZERO_STRING, -} from '../base64' -import { - Base64BitflagIterator, - EnumerateFlags, - useIterator, -} from '../enumeration' +import { decodeB64Byte, encodeB64Byte, normaliseB64String, ZERO_STRING } from '../base64' +import { Base64BitflagIterator, EnumerateFlags, useIterator } from '../enumeration' /** * Provides flags that are stored in strings using a little-endian base 64 diff --git a/node/src/flagsets/bigint.ts b/node/src/flagsets/bigint.ts index c8f87c0..a7cc821 100644 --- a/node/src/flagsets/bigint.ts +++ b/node/src/flagsets/bigint.ts @@ -1,11 +1,7 @@ import type { FlagSet } from '.' import { UnavailableFeatureError } from '../errors' import { ENV_BI } from '../env' -import { - BigBitFlagsIterator, - EnumerateFlags, - useIterator, -} from '../enumeration' +import { BigBitFlagsIterator, EnumerateFlags, useIterator } from '../enumeration' export class BigBitFlagSet implements FlagSet { /** diff --git a/node/src/flagsets/index.ts b/node/src/flagsets/index.ts index 7401ae4..fd152ad 100644 --- a/node/src/flagsets/index.ts +++ b/node/src/flagsets/index.ts @@ -67,7 +67,7 @@ export interface FlagSet { /** * Checks whether the first set of flags includes at least one of the flags - * from the second set. + * from the second set. If there is no required flags, the result will always be `false`. * * A flag is considered to be part of the set only if all of its parents are * present too. @@ -79,7 +79,7 @@ export interface FlagSet { /** * Checks whether the first set of flags includes all the flags from the - * second set. + * second set. If there is no required flags, the result will always be `true`. * * A flag is considered to be part of the set only if all of its parents are * present too. diff --git a/node/src/flagsets/number.ts b/node/src/flagsets/number.ts index f99eab5..299a1c0 100644 --- a/node/src/flagsets/number.ts +++ b/node/src/flagsets/number.ts @@ -1,6 +1,6 @@ import type { FlagSet } from '.' -import { BitFlagsIterator, EnumerateFlags, useIterator } from '../enumeration' -import { FlagDefinition, FlagsDictionary } from '../definitions' +import { BitFlagsIterator, EnumerateFlags, useIterator } from '~/enumeration' +import { FlagDefinition, FlagsDictionary } from '~/definitions' export class BitFlagSet implements FlagSet { private readonly _dictionary: FlagsDictionary @@ -18,10 +18,7 @@ export class BitFlagSet implements FlagSet { } public named(...aliases: string[]): number { - return aliases.reduce( - (set, alias) => set | (this.getFlag(alias)?.values ?? 0), - 0, - ) + return aliases.reduce((set, alias) => set | (this.getFlag(alias)?.values ?? 0), 0) } public union(first: number, second: number): number { @@ -37,15 +34,15 @@ export class BitFlagSet implements FlagSet { } public isSuperset(first: number, second: number): boolean { - return (first & second) == second + return (first & second) === second } public hasAny(flags: number, required: number): boolean { - return false + return this.minimum(this.intersection(flags, required)) !== 0 } public hasAll(flags: number, required: number): boolean { - return false + return this.isSuperset(flags, this.maximum(required)) } public enumerate(flags: number): EnumerateFlags { @@ -53,11 +50,25 @@ export class BitFlagSet implements FlagSet { } public maximum(flags: number): number { - return 0 + let result = this.none() + for (const value of this.enumerate(flags)) { + const flag = this._dictionary.findByValue(value) + if (flag !== undefined) { + result = flag.addTo(result) + } + } + return result } public minimum(flags: number): number { - return 0 + let result = this.none() + for (const value of this.enumerate(flags)) { + const flag = this._dictionary.findByValue(value) + if (flag !== undefined && flag.isIn(flags)) { + result = flag.addTo(result) + } + } + return result } public getFlag(alias: string): FlagDefinition | undefined { diff --git a/node/src/index.ts b/node/src/index.ts index 0e85362..bb7df46 100644 --- a/node/src/index.ts +++ b/node/src/index.ts @@ -1,5 +1,5 @@ -export { FlagSetBuilder, createBitFlagSet } from './builders' -export { FlagDefinition, FlagsDictionary } from './definitions' +export { createBitFlagSet } from './builders' +export { FlagDefinition } from './definitions' export { InvalidBitFlagValueError } from './errors' export { ArrayFlagSet, diff --git a/node/tests/definitions/dictionary.test.ts b/node/tests/definitions/dictionary.test.ts deleted file mode 100644 index 7c249ca..0000000 --- a/node/tests/definitions/dictionary.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { FlagDefinition, FlagsDictionary } from '~' -import { describe, expect, test } from 'vitest' -import { ReusedFlagAliasError, ReusedFlagValueError } from '../../src/errors' - -class TestDefinition implements FlagDefinition { - private readonly _value: number - - public constructor(value: number) { - this._value = value - } - - public get values(): unknown { - return this._value - } - - public hasSameValue(other: FlagDefinition): boolean { - return other instanceof TestDefinition && this._value === other._value - } -} - -describe(FlagsDictionary, () => { - test('define then look up', () => { - const dict = new FlagsDictionary() - const def = new TestDefinition(1) - - dict.define('test', def) - - expect(dict.findByAlias('test')).toBe(def) - expect(dict.findByAlias('undefined')).toBe(undefined) - }) - - test("can't use the same alias twice", () => { - const dict = new FlagsDictionary() - - dict.define('test', new TestDefinition(1)) - expect(() => dict.define('test', new TestDefinition(2))).toThrow( - ReusedFlagAliasError, - ) - }) - - test("can't use the same value twice", () => { - const dict = new FlagsDictionary() - - dict.define('test A', new TestDefinition(1)) - expect(() => dict.define('test B', new TestDefinition(1))).toThrow( - ReusedFlagValueError, - ) - }) -}) diff --git a/node/tests/definitions/graph.test.ts b/node/tests/definitions/graph.test.ts new file mode 100644 index 0000000..a61c666 --- /dev/null +++ b/node/tests/definitions/graph.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, test } from 'vitest' +import { FlagsGraph } from '~/definitions/graph' + +describe(FlagsGraph, () => { + test('put a definition in the graph', () => { + const graph = new FlagsGraph() + + const A = graph.put({ alias: 'A', value: 1 }) + + expect(graph.definitions).toHaveLength(1) + expect(graph.definitions).toContain(A) + + expect(A.isPlaceholder).toBe(false) + expect(A.alias).toBe('A') + expect(A.value).toBe(1) + }) + + test('create a definition with a reference to an existing definition', () => { + const graph = new FlagsGraph() + + const A = graph.put({ alias: 'A', value: 1 }) + + expect(graph.definitions).toHaveLength(1) + expect(graph.definitions).toContain(A) + + const B = graph.put({ alias: 'B', value: 2, parents: [{ alias: 'A' }] }) + + expect(graph.definitions).toHaveLength(2) + expect(graph.definitions).toContain(B) + + expect(B.parents).toContain(A) + expect(A.children).toContain(B) + }) + + test('create a definition with a forward reference', () => { + const graph = new FlagsGraph() + + const B = graph.put({ alias: 'B', value: 2, parents: [{ alias: 'A' }] }) + + expect(graph.definitions).toHaveLength(1) + expect(graph.definitions).toContain(B) + + const A = graph.put({ alias: 'A', value: 1 }) + + expect(graph.definitions).toHaveLength(2) + expect(graph.definitions).toContain(A) + expect(A.value).toBe(1) + + expect(B.parents).toContain(A) + expect(A.children).toContain(B) + }) +}) diff --git a/node/tests/flagsets/array.test.ts b/node/tests/flagsets/array.test.ts index 491e3d9..08ecbf3 100644 --- a/node/tests/flagsets/array.test.ts +++ b/node/tests/flagsets/array.test.ts @@ -36,10 +36,7 @@ describe(ArrayFlagSet, () => { expect(flags.intersection(['A'], ['B'])).toEqual([]) expect(flags.intersection(['A'], ['A', 'B'])).toEqual(['A']) expect(flags.intersection(['A', 'B', 'D'], ['A', 'C'])).toEqual(['A']) - expect(flags.intersection(['A', 'B', 'D'], ['A', 'B', 'C'])).toEqual([ - 'A', - 'B', - ]) + expect(flags.intersection(['A', 'B', 'D'], ['A', 'B', 'C'])).toEqual(['A', 'B']) }) test('isSuperset', () => { diff --git a/node/tests/flagsets/collection.test.ts b/node/tests/flagsets/collection.test.ts index b9a6233..e2964a9 100644 --- a/node/tests/flagsets/collection.test.ts +++ b/node/tests/flagsets/collection.test.ts @@ -19,9 +19,7 @@ describe(CollectionFlagSet, () => { expect(flags.union(set('A'), set())).toEqual(set('A')) expect(flags.union(set(), set('B'))).toEqual(set('B')) expect(flags.union(set('A'), set('B'))).toEqual(set('A', 'B')) - expect(flags.union(set('A', 'B'), set('B', 'C'))).toEqual( - set('A', 'B', 'C'), - ) + expect(flags.union(set('A', 'B'), set('B', 'C'))).toEqual(set('A', 'B', 'C')) }) test('difference', () => { @@ -41,12 +39,8 @@ describe(CollectionFlagSet, () => { expect(flags.intersection(set('A'), set())).toEqual(set()) expect(flags.intersection(set('A'), set('B'))).toEqual(set()) expect(flags.intersection(set('A'), set('A', 'B'))).toEqual(set('A')) - expect(flags.intersection(set('A', 'B', 'D'), set('A', 'C'))).toEqual( - set('A'), - ) - expect( - flags.intersection(set('A', 'B', 'D'), set('A', 'B', 'C')), - ).toEqual(set('A', 'B')) + expect(flags.intersection(set('A', 'B', 'D'), set('A', 'C'))).toEqual(set('A')) + expect(flags.intersection(set('A', 'B', 'D'), set('A', 'B', 'C'))).toEqual(set('A', 'B')) }) test('isSuperset', () => { @@ -65,10 +59,6 @@ describe(CollectionFlagSet, () => { expect([...flags.enumerate(set())]).toEqual([]) expect([...flags.enumerate(set('A'))]).toEqual(['A']) - expect([...flags.enumerate(set('A', 'B', 'C'))]).toEqual([ - 'A', - 'B', - 'C', - ]) + expect([...flags.enumerate(set('A', 'B', 'C'))]).toEqual(['A', 'B', 'C']) }) }) diff --git a/node/tests/flagsets/number.test.ts b/node/tests/flagsets/number.test.ts index f324f70..e9620aa 100644 --- a/node/tests/flagsets/number.test.ts +++ b/node/tests/flagsets/number.test.ts @@ -75,6 +75,38 @@ describe(BitFlagSet, () => { expect(flags.isSuperset(8, 4)).toBe(false) }) + test('hasAny', () => { + const flags = createBitFlagSet([ + { value: 1, as: 'A' }, + { value: 2, as: 'B', requires: ['A'] }, + { value: 4, as: 'C', requires: ['A'] }, + { value: 8, as: 'D', requires: ['B', 'C'] }, + ]) + + expect(flags.hasAny(15, 0)).toBe(false) + expect(flags.hasAny(0, 0)).toBe(false) + expect(flags.hasAny(7, 1)).toBe(true) + expect(flags.hasAny(1, 7)).toBe(true) + expect(flags.hasAny(5, 12)).toBe(false) + expect(flags.hasAny(2, 2)).toBe(false) + }) + + test('hasAll', () => { + const flags = createBitFlagSet([ + { value: 1, as: 'A' }, + { value: 2, as: 'B', requires: ['A'] }, + { value: 4, as: 'C', requires: ['A'] }, + { value: 8, as: 'D', requires: ['B', 'C'] }, + ]) + + expect(flags.hasAll(15, 0)).toBe(true) + expect(flags.hasAll(0, 0)).toBe(true) + expect(flags.hasAll(7, 2)).toBe(true) + expect(flags.hasAll(6, 2)).toBe(false) + expect(flags.hasAll(1, 7)).toBe(false) + expect(flags.hasAll(5, 12)).toBe(false) + }) + test('enumerate', () => { const flags = createBitFlagSet([]) @@ -85,4 +117,36 @@ describe(BitFlagSet, () => { expect([...flags.enumerate(11)]).toEqual([1, 2, 8]) expect([...flags.enumerate(100)]).toEqual([4, 32, 64]) }) + + test('maximum', () => { + const flags = createBitFlagSet([ + { value: 1, as: 'A' }, + { value: 2, as: 'B', requires: ['A'] }, + { value: 4, as: 'C', requires: ['A'] }, + { value: 8, as: 'D', requires: ['B', 'C'] }, + ]) + + expect(flags.maximum(0)).toEqual(0) + expect(flags.maximum(1)).toEqual(1) + expect(flags.maximum(2)).toEqual(3) + expect(flags.maximum(3)).toEqual(3) + expect(flags.maximum(4)).toEqual(5) + expect(flags.maximum(8)).toEqual(15) + }) + + test('minimum', () => { + const flags = createBitFlagSet([ + { value: 1, as: 'A' }, + { value: 2, as: 'B', requires: ['A'] }, + { value: 4, as: 'C', requires: ['A'] }, + { value: 8, as: 'D', requires: ['B', 'C'] }, + ]) + + expect(flags.minimum(0)).toEqual(0) + expect(flags.minimum(1)).toEqual(1) + expect(flags.minimum(2)).toEqual(0) + expect(flags.minimum(3)).toEqual(3) + expect(flags.minimum(4)).toEqual(0) + expect(flags.minimum(13)).toEqual(5) + }) }) diff --git a/node/tsconfig.json b/node/tsconfig.json index cdb3bf1..76922c9 100644 --- a/node/tsconfig.json +++ b/node/tsconfig.json @@ -1,111 +1,113 @@ { - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + /* Language and Environment */ + "target": "es2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - "paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */ - "~": ["./src/index.ts"] - }, - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + /* Modules */ + "module": "commonjs" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + "paths": { + /* Specify a set of entries that re-map imports to additional lookup locations. */ + "~": ["./src/index.ts"], + "~/*": ["./src/*"] + }, + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + "resolveJsonModule": true /* Enable importing .json files. */, + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./dist", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist" /* Specify an output folder for all emitted files. */, + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + "stripInternal": true /* Disable emitting declarations that have '@internal' in their JSDoc comments. */, + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + "noImplicitThis": true /* Enable error reporting when 'this' is given the type 'any'. */, + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + "noImplicitOverride": true /* Ensure overriding members in derived classes are marked with an override modifier. */, + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } } diff --git a/node/vitest.config.ts b/node/vitest.config.ts index 0a0cd34..4239e5f 100644 --- a/node/vitest.config.ts +++ b/node/vitest.config.ts @@ -3,7 +3,7 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { alias: { - '~': 'src/index.ts', + '~': 'src', }, }, })