From 2a3dd23f7971cd9abb1ef0e1a800be58404c392e Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Sat, 8 Nov 2025 22:46:15 +0100 Subject: [PATCH 1/3] fix: improve types of generated index.d.ts --- src/collection.test.ts | 62 +++++++++++++++++++++++++++++++++++++++ src/collection.ts | 45 +++++++++++++++++++++------- src/context-collection.ts | 14 ++++----- src/index.ts | 36 +++++++++++++++-------- vite.config.js | 6 ++-- 5 files changed, 130 insertions(+), 33 deletions(-) create mode 100644 src/collection.test.ts diff --git a/src/collection.test.ts b/src/collection.test.ts new file mode 100644 index 0000000..f3574d2 --- /dev/null +++ b/src/collection.test.ts @@ -0,0 +1,62 @@ +import { test, expect, expectTypeOf } from 'vitest' +import { Collection } from './collection' + +let loc = { start: { line: 1, column: 1, offset: 1 }, end: { offset: 2 } } + +test('basic collection - size()', () => { + let collection = new Collection() + collection.p('a', loc) + collection.p('a', loc) + expect(collection.size()).toEqual(2) +}) + +test('count with useLocations=undefined', () => { + let collection = new Collection() + collection.p('a', loc) + collection.p('a', loc) + + let count = collection.c() + expect(count).toEqual({ + total: 2, + totalUnique: 1, + unique: { + a: 2, + }, + uniquenessRatio: 0.5, + }) + expectTypeOf(count['uniqueWithLocations']).toBeUndefined() +}) + +test('count with useLocations=false', () => { + let collection = new Collection(false) + collection.p('a', loc) + collection.p('a', loc) + + let count = collection.c() + expect(count).toEqual({ + total: 2, + totalUnique: 1, + unique: { + a: 2, + }, + uniquenessRatio: 0.5, + }) + expectTypeOf(count['uniqueWithLocations']).toBeUndefined() +}) + +test('count with useLocations=false', () => { + let collection = new Collection(true) + collection.p('a', loc) + collection.p('a', loc) + + let pos = { offset: 1, length: 1, line: 1, column: 1 } + let count = collection.c() + expect(count).toEqual({ + total: 2, + totalUnique: 1, + unique: {}, + uniquenessRatio: 0.5, + uniqueWithLocations: { a: [pos, pos] }, + }) + expectTypeOf(count['uniqueWithLocations']).not.toBeUndefined() +}) diff --git a/src/collection.ts b/src/collection.ts index 6400c02..ebf9e7e 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -1,12 +1,26 @@ -import type { CssLocation } from 'css-tree' +export type Location = { + line: number + column: number + offset: number + length: number +} + +export type CollectionCount = { + total: number + totalUnique: number + unique: Record + uniquenessRatio: number +} & (WithLocations extends true + ? { uniqueWithLocations: Record } + : { uniqueWithLocations?: undefined }) -export class Collection { +export class Collection { #items: Map #total: number #nodes: number[] = [] - #useLocations: boolean + #useLocations: UseLocations - constructor(useLocations = false) { + constructor(useLocations: UseLocations = false as UseLocations) { this.#items = new Map() this.#total = 0 @@ -46,8 +60,8 @@ export class Collection { return this.#total } - c() { - let uniqueWithLocations: Map = new Map() + c(): CollectionCount { + let uniqueWithLocations: Map = new Map() let unique: Record = {} let useLocations = this.#useLocations let items = this.#items @@ -72,14 +86,23 @@ export class Collection { }) let total = this.#total - let data = { + + if (useLocations) { + return { + total, + totalUnique: size, + unique, + uniquenessRatio: total === 0 ? 0 : size / total, + uniqueWithLocations: Object.fromEntries(uniqueWithLocations), + } as unknown as CollectionCount + } + + return { total, totalUnique: size, unique, uniquenessRatio: total === 0 ? 0 : size / total, - uniqueWithLocations: useLocations ? Object.fromEntries(uniqueWithLocations) : undefined, - } - - return data + uniqueWithLocations: undefined, + } as unknown as CollectionCount } } diff --git a/src/context-collection.ts b/src/context-collection.ts index 45fd9e8..e6d13ee 100644 --- a/src/context-collection.ts +++ b/src/context-collection.ts @@ -1,12 +1,12 @@ import type { CssLocation } from 'css-tree' -import { Collection } from './collection.js' +import { Collection, type CollectionCount } from './collection.js' -export class ContextCollection { - #list: Collection - #contexts: Map - #useLocations: boolean +export class ContextCollection { + #list: Collection + #contexts: Map> + #useLocations: UseLocations - constructor(useLocations: boolean) { + constructor(useLocations: UseLocations) { this.#list = new Collection(useLocations) this.#contexts = new Map() this.#useLocations = useLocations @@ -29,7 +29,7 @@ export class ContextCollection { } count() { - let itemsPerContext = new Map() + let itemsPerContext: Map> = new Map() for (let [context, value] of this.#contexts.entries()) { itemsPerContext.set(context, value.c()) diff --git a/src/index.ts b/src/index.ts index 4ec977f..650a8f4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -43,18 +43,22 @@ function ratio(part: number, total: number): number { return part / total } -let defaults = { - useLocations: false, -} - -type Options = { +export type Options = { /** @description Use Locations (`{ 'item': [{ line, column, offset, length }] }`) instead of a regular count per occurrence (`{ 'item': 3 }`) */ useLocations?: boolean } -export function analyze(css: string, options: Options = {}) { - let settings = Object.assign({}, defaults, options) - let useLocations = settings.useLocations === true +export function analyze(css: string, options?: Options & { useLocations?: false | undefined }): ReturnType> +export function analyze(css: string, options: Options & { useLocations: true }): ReturnType> +export function analyze(css: string, options: Options = {}): any { + const useLocations = options.useLocations === true + if (useLocations) { + return analyzeInternal(css, options, true) + } + return analyzeInternal(css, options, false) +} + +function analyzeInternal(css: string, options: Options, useLocations: T) { let start = Date.now() /** @@ -77,8 +81,14 @@ export function analyze(css: string, options: Options = {}) { let embedSize = 0 let embedTypes = { total: 0, - /** @type {Map} */ - unique: new Map(), + unique: new Map() as Map< + string, + { + size: number + count: number + uniqueWithLocations?: { offset: number; line: number; column: number; length: number }[] + } + >, } let startParse = Date.now() @@ -96,7 +106,7 @@ export function analyze(css: string, options: Options = {}) { let linesOfCode = ast.loc.end.line - ast.loc.start.line + 1 // Atrules - let atrules = new Collection(useLocations) + let atrules = new Collection(useLocations) as Collection let atRuleComplexities = new AggregateCollection() /** @type {Record[]} */ let fontfaces: Record[] = [] @@ -430,11 +440,11 @@ export function analyze(css: string, options: Options = {}) { } if (embedTypes.unique.has(type)) { - let item = embedTypes.unique.get(type) + let item = embedTypes.unique.get(type)! item.count++ item.size += size embedTypes.unique.set(type, item) - if (useLocations) { + if (useLocations && item.uniqueWithLocations) { item.uniqueWithLocations.push(loc) } } else { diff --git a/vite.config.js b/vite.config.js index c61a54b..f8217e1 100644 --- a/vite.config.js +++ b/vite.config.js @@ -6,7 +6,7 @@ import { codecovVitePlugin } from '@codecov/vite-plugin' export default defineConfig({ build: { lib: { - entry: resolve(__dirname, 'src/index.js'), + entry: resolve(__dirname, 'src/index.ts'), formats: ['es'], }, rollupOptions: { @@ -16,7 +16,9 @@ export default defineConfig({ }, }, plugins: [ - dts(), + dts({ + rollupTypes: true, + }), codecovVitePlugin({ enableBundleAnalysis: process.env.CODECOV_TOKEN !== undefined, bundleName: 'analyzeCss', From d73b643c942107baa54158579ed14815b75e3f1d Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Sat, 8 Nov 2025 22:49:57 +0100 Subject: [PATCH 2/3] more improve --- src/collection.test.ts | 6 +++--- src/collection.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/collection.test.ts b/src/collection.test.ts index f3574d2..ad8057c 100644 --- a/src/collection.test.ts +++ b/src/collection.test.ts @@ -1,5 +1,5 @@ import { test, expect, expectTypeOf } from 'vitest' -import { Collection } from './collection' +import { Collection, type UniqueWithLocations } from './collection' let loc = { start: { line: 1, column: 1, offset: 1 }, end: { offset: 2 } } @@ -44,7 +44,7 @@ test('count with useLocations=false', () => { expectTypeOf(count['uniqueWithLocations']).toBeUndefined() }) -test('count with useLocations=false', () => { +test('count with useLocations=true', () => { let collection = new Collection(true) collection.p('a', loc) collection.p('a', loc) @@ -58,5 +58,5 @@ test('count with useLocations=false', () => { uniquenessRatio: 0.5, uniqueWithLocations: { a: [pos, pos] }, }) - expectTypeOf(count['uniqueWithLocations']).not.toBeUndefined() + expectTypeOf(count['uniqueWithLocations']).toMatchObjectType() }) diff --git a/src/collection.ts b/src/collection.ts index ebf9e7e..204ea71 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -5,14 +5,14 @@ export type Location = { length: number } +export type UniqueWithLocations = Record + export type CollectionCount = { total: number totalUnique: number unique: Record uniquenessRatio: number -} & (WithLocations extends true - ? { uniqueWithLocations: Record } - : { uniqueWithLocations?: undefined }) +} & (WithLocations extends true ? { uniqueWithLocations: UniqueWithLocations } : { uniqueWithLocations?: undefined }) export class Collection { #items: Map From 4f5aa68312b6dcd50028a8d8297c544b0765040e Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Sat, 8 Nov 2025 22:53:27 +0100 Subject: [PATCH 3/3] rm --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 650a8f4..a6b6770 100644 --- a/src/index.ts +++ b/src/index.ts @@ -106,7 +106,7 @@ function analyzeInternal(css: string, options: Options, useLo let linesOfCode = ast.loc.end.line - ast.loc.start.line + 1 // Atrules - let atrules = new Collection(useLocations) as Collection + let atrules = new Collection(useLocations) let atRuleComplexities = new AggregateCollection() /** @type {Record[]} */ let fontfaces: Record[] = []