Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/collection.test.ts
Original file line number Diff line number Diff line change
@@ -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<UniqueWithLocations>()
})
45 changes: 34 additions & 11 deletions src/collection.ts
Original file line number Diff line number Diff line change
@@ -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<string, Location[]>

export type CollectionCount<WithLocations extends boolean = false> = {
total: number
totalUnique: number
unique: Record<string, number>
uniquenessRatio: number
} & (WithLocations extends true ? { uniqueWithLocations: UniqueWithLocations } : { uniqueWithLocations?: undefined })

export class Collection {
export class Collection<UseLocations extends boolean = false> {
#items: Map<string | number, number[]>
#total: number
#nodes: number[] = []
#useLocations: boolean
#useLocations: UseLocations

constructor(useLocations = false) {
constructor(useLocations: UseLocations = false as UseLocations) {
this.#items = new Map()
this.#total = 0

Expand Down Expand Up @@ -46,8 +60,8 @@ export class Collection {
return this.#total
}

c() {
let uniqueWithLocations: Map<string | number, { line: number; column: number; offset: number; length: number }[]> = new Map()
c(): CollectionCount<UseLocations> {
let uniqueWithLocations: Map<string | number, Location[]> = new Map()
let unique: Record<string, number> = {}
let useLocations = this.#useLocations
let items = this.#items
Expand All @@ -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<UseLocations>
}

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<UseLocations>
}
}
14 changes: 7 additions & 7 deletions src/context-collection.ts
Original file line number Diff line number Diff line change
@@ -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<string, Collection>
#useLocations: boolean
export class ContextCollection<UseLocations extends boolean = false> {
#list: Collection<UseLocations>
#contexts: Map<string, Collection<UseLocations>>
#useLocations: UseLocations

constructor(useLocations: boolean) {
constructor(useLocations: UseLocations) {
this.#list = new Collection(useLocations)
this.#contexts = new Map()
this.#useLocations = useLocations
Expand All @@ -29,7 +29,7 @@ export class ContextCollection {
}

count() {
let itemsPerContext = new Map()
let itemsPerContext: Map<string, CollectionCount<UseLocations>> = new Map()

for (let [context, value] of this.#contexts.entries()) {
itemsPerContext.set(context, value.c())
Expand Down
34 changes: 22 additions & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof analyzeInternal<false>>
export function analyze(css: string, options: Options & { useLocations: true }): ReturnType<typeof analyzeInternal<true>>
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<T extends boolean>(css: string, options: Options, useLocations: T) {
let start = Date.now()

/**
Expand All @@ -77,8 +81,14 @@ export function analyze(css: string, options: Options = {}) {
let embedSize = 0
let embedTypes = {
total: 0,
/** @type {Map<string, { size: number, count: number } & ({ uniqueWithLocations?: undefined } | ({ uniqueWithLocations: { offset: number, line: number, column: number, length: number }[] })) }>} */
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()
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 4 additions & 2 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -16,7 +16,9 @@ export default defineConfig({
},
},
plugins: [
dts(),
dts({
rollupTypes: true,
}),
codecovVitePlugin({
enableBundleAnalysis: process.env.CODECOV_TOKEN !== undefined,
bundleName: 'analyzeCss',
Expand Down