diff --git a/src/collection.test.ts b/src/collection.test.ts new file mode 100644 index 0000000..ad8057c --- /dev/null +++ b/src/collection.test.ts @@ -0,0 +1,62 @@ +import { test, expect, expectTypeOf } from 'vitest' +import { Collection, type UniqueWithLocations } 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=true', () => { + 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']).toMatchObjectType() +}) diff --git a/src/collection.ts b/src/collection.ts index 6400c02..204ea71 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 UniqueWithLocations = Record + +export type CollectionCount = { + total: number + totalUnique: number + unique: Record + uniquenessRatio: number +} & (WithLocations extends true ? { uniqueWithLocations: UniqueWithLocations } : { 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..a6b6770 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() @@ -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',