From 83b9a53a0e8e7c2192cbc5ccfa3fc86d5879b64e Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Tue, 2 Jun 2026 13:26:47 +0200 Subject: [PATCH 1/3] test: fix outdated tests --- src/unplugin/transform.test.ts | 647 +++++++++++++++++++------------- src/unplugin/transform.ts | 259 +++++++++---- src/unplugin/tree-shake.test.ts | 61 +-- 3 files changed, 602 insertions(+), 365 deletions(-) diff --git a/src/unplugin/transform.test.ts b/src/unplugin/transform.test.ts index 0d85dd7..d20dc9a 100644 --- a/src/unplugin/transform.test.ts +++ b/src/unplugin/transform.test.ts @@ -1,12 +1,26 @@ +import type { TrackedExportsMap, TransformOptions } from './transform' +import { join } from 'node:path' import { describe, expect, it } from 'vitest' import { transform } from './transform' -function expectTransform(input: string, expected: string) { - const result = transform(input, 'test.ts') +const CALLSITE_ID = join(import.meta.dirname, '../../demo-lib/src/math.ts') + +function expectDefinitionTransform(input: string, expected: string, options?: TransformOptions) { + const result = transform(input, 'test.ts', options) + expect(result).toBeDefined() + expect(result!.code).toBe(expected) +} + +function expectCallSiteTransform(input: string, expected: string) { + const result = transform(input, CALLSITE_ID, undefined, new Map()) expect(result).toBeDefined() expect(result!.code).toBe(expected) } +function expectCallSiteUnchanged(input: string) { + expect(transform(input, CALLSITE_ID, undefined, new Map())).toBeUndefined() +} + describe('transform', () => { describe('import detection', () => { it('returns undefined for files without nostics', () => { @@ -17,405 +31,522 @@ describe('transform', () => { expect(transform('const x = "nostics"', 'test.ts')).toBeUndefined() }) - it('detects named imports from nostics', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -log.E1().warn()` - - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -process.env.NODE_ENV !== 'production' && log.E1().warn()` + it('does not track unrelated nostics imports', () => { + const input = `import { formatDiagnostic } from 'nostics' +const formatted = formatDiagnostic(diagnostic) +formatted.trim()` - expectTransform(input, expected) + expect(transform(input, 'test.ts')).toBeUndefined() }) + }) - it('handles renamed imports', () => { - const input = `import { createLogger as myLog } from 'nostics' -const log = myLog({}) -log.E1().warn()` + describe('pure annotations', () => { + it('adds /*#__PURE__*/ to defineDiagnostics calls', () => { + const input = `import { defineDiagnostics } from 'nostics' +const diagnostics = defineDiagnostics({ codes: {} })` - const expected = `import { createLogger as myLog } from 'nostics' -const log = /*#__PURE__*/ myLog({}) -process.env.NODE_ENV !== 'production' && log.E1().warn()` + const expected = `import { defineDiagnostics } from 'nostics' +const diagnostics = /*#__PURE__*/ defineDiagnostics({ codes: {} })` - expectTransform(input, expected) + expectDefinitionTransform(input, expected) }) - }) - describe('pURE annotations', () => { - it('adds /*#__PURE__*/ to defineDiagnostics calls', () => { + it('adds /*#__PURE__*/ to exported defineDiagnostics calls', () => { const input = `import { defineDiagnostics } from 'nostics' -const diags = defineDiagnostics({ codes: {} })` +export const diagnostics = defineDiagnostics({ codes: {} })` const expected = `import { defineDiagnostics } from 'nostics' -const diags = /*#__PURE__*/ defineDiagnostics({ codes: {} })` +export const diagnostics = /*#__PURE__*/ defineDiagnostics({ codes: {} })` - expectTransform(input, expected) + expectDefinitionTransform(input, expected) }) - it('adds /*#__PURE__*/ to createLogger calls', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({ diagnostics: [] })` + it('handles renamed defineDiagnostics imports', () => { + const input = `import { defineDiagnostics as define } from 'nostics' +export const diagnostics = define({ codes: {} })` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({ diagnostics: [] })` + const expected = `import { defineDiagnostics as define } from 'nostics' +export const diagnostics = /*#__PURE__*/ define({ codes: {} })` - expectTransform(input, expected) + expectDefinitionTransform(input, expected) }) - it('adds /*#__PURE__*/ to both defineDiagnostics and createLogger', () => { - const input = `import { defineDiagnostics, createLogger } from 'nostics' -const diags = defineDiagnostics({ codes: { E1: { message: 'x' } } }) -const log = createLogger({ diagnostics: [diags] })` + it('supports custom package name', () => { + const input = `import { defineDiagnostics } from 'my-custom-sdk' +export const diagnostics = defineDiagnostics({ codes: {} })` - const expected = `import { defineDiagnostics, createLogger } from 'nostics' -const diags = /*#__PURE__*/ defineDiagnostics({ codes: { E1: { message: 'x' } } }) -const log = /*#__PURE__*/ createLogger({ diagnostics: [diags] })` + const expected = `import { defineDiagnostics } from 'my-custom-sdk' +export const diagnostics = /*#__PURE__*/ defineDiagnostics({ codes: {} })` - expectTransform(input, expected) + expectDefinitionTransform(input, expected, { packageName: 'my-custom-sdk' }) }) }) - describe('expression statement wrapping', () => { - it('wraps log.CODE().method() calls', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -log.E1().warn()` + describe('cross-file tracking', () => { + it('wraps diagnostics imported from a relative module', () => { + const input = `import { diagnostics } from './diagnostics' +export function run() { + diagnostics.E1() +}` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -process.env.NODE_ENV !== 'production' && log.E1().warn()` + const expected = `import { diagnostics } from './diagnostics' +export function run() { + process.env.NODE_ENV !== 'production' && diagnostics.E1() +}` - expectTransform(input, expected) + expectCallSiteTransform(input, expected) }) - it('wraps chained calls with arguments', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -log.B2011({ src: '/bad.ts' }).warn()` + it('uses the shared tracked exports cache when present', () => { + const trackedExportsMap: TrackedExportsMap = new Map() + const diagnosticsId = join(import.meta.dirname, '../../demo-lib/src/diagnostics.ts') + + transform( + `import { defineDiagnostics } from 'nostics' +export const diagnostics = defineDiagnostics({ codes: {} })`, + diagnosticsId, + undefined, + trackedExportsMap, + ) - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -process.env.NODE_ENV !== 'production' && log.B2011({ src: '/bad.ts' }).warn()` + const input = `import { diagnostics } from './diagnostics' +diagnostics.E1()` - expectTransform(input, expected) + const expected = `import { diagnostics } from './diagnostics' +process.env.NODE_ENV !== 'production' && diagnostics.E1()` + + const result = transform(input, CALLSITE_ID, undefined, trackedExportsMap) + expect(result).toBeDefined() + expect(result!.code).toBe(expected) }) - it('wraps raw logger methods', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -log.warn(someDiagnostic)` + it('tracks renamed relative imports', () => { + const input = `import { diagnostics as diag } from './diagnostics' +diag.E1()` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -process.env.NODE_ENV !== 'production' && log.warn(someDiagnostic)` + const expected = `import { diagnostics as diag } from './diagnostics' +process.env.NODE_ENV !== 'production' && diag.E1()` - expectTransform(input, expected) + expectCallSiteTransform(input, expected) + }) + + it('does not wrap imports from non-relative modules', () => { + const input = `import { diagnostics } from 'demo-lib/diagnostics' +diagnostics.E1()` + + expect(transform(input, CALLSITE_ID, undefined, new Map())).toBeUndefined() + }) + }) + + describe('expression statement wrapping', () => { + it('wraps diagnostic handle calls', () => { + const input = `import { diagnostics } from './diagnostics' +diagnostics.E1()` + + const expected = `import { diagnostics } from './diagnostics' +process.env.NODE_ENV !== 'production' && diagnostics.E1()` + + expectCallSiteTransform(input, expected) + }) + + it('wraps diagnostic handle calls with arguments', () => { + const input = `import { diagnostics } from './diagnostics' +diagnostics.B2011({ src: '/bad.ts' })` + + const expected = `import { diagnostics } from './diagnostics' +process.env.NODE_ENV !== 'production' && diagnostics.B2011({ src: '/bad.ts' })` + + expectCallSiteTransform(input, expected) }) it('wraps multiple expression statements', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -log.E1().warn() -log.E2().error() -log.E3().log()` + const input = `import { diagnostics } from './diagnostics' +diagnostics.E1() +diagnostics.E2() +diagnostics.E3()` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -process.env.NODE_ENV !== 'production' && log.E1().warn() -process.env.NODE_ENV !== 'production' && log.E2().error() -process.env.NODE_ENV !== 'production' && log.E3().log()` + const expected = `import { diagnostics } from './diagnostics' +process.env.NODE_ENV !== 'production' && diagnostics.E1() +process.env.NODE_ENV !== 'production' && diagnostics.E2() +process.env.NODE_ENV !== 'production' && diagnostics.E3()` - expectTransform(input, expected) + expectCallSiteTransform(input, expected) }) }) - describe('nested scopes', () => { + describe('nested usages', () => { it('transforms inside function bodies', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) + const input = `import { diagnostics } from './diagnostics' function handler() { - log.E1().warn() + diagnostics.E1() }` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) + const expected = `import { diagnostics } from './diagnostics' function handler() { - process.env.NODE_ENV !== 'production' && log.E1().warn() + process.env.NODE_ENV !== 'production' && diagnostics.E1() }` - expectTransform(input, expected) + expectCallSiteTransform(input, expected) }) it('transforms inside if blocks', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) + const input = `import { diagnostics } from './diagnostics' if (condition) { - log.E1().warn() + diagnostics.E1() }` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) + const expected = `import { diagnostics } from './diagnostics' if (condition) { - process.env.NODE_ENV !== 'production' && log.E1().warn() + process.env.NODE_ENV !== 'production' && diagnostics.E1() }` - expectTransform(input, expected) + expectCallSiteTransform(input, expected) }) - it('transforms function-scoped logger creation', () => { - const input = `import { defineDiagnostics, createLogger } from 'nostics' -function setup() { - const diags = defineDiagnostics({ codes: {} }) - const log = createLogger({ diagnostics: [diags] }) - log.E1().warn() -}` + it('does not transform a function parameter that shadows diagnostics', () => { + const input = `import { diagnostics } from './diagnostics' +function setup(diagnostics) { + diagnostics.E1() +} +diagnostics.E2()` - const expected = `import { defineDiagnostics, createLogger } from 'nostics' -function setup() { - const diags = /*#__PURE__*/ defineDiagnostics({ codes: {} }) - const log = /*#__PURE__*/ createLogger({ diagnostics: [diags] }) - process.env.NODE_ENV !== 'production' && log.E1().warn() -}` + const expected = `import { diagnostics } from './diagnostics' +function setup(diagnostics) { + diagnostics.E1() +} +process.env.NODE_ENV !== 'production' && diagnostics.E2()` - expectTransform(input, expected) + expectCallSiteTransform(input, expected) + }) + + it('does not transform a block binding that shadows diagnostics', () => { + const input = `import { diagnostics } from './diagnostics' +if (condition) { + const diagnostics = getDiagnostics() + diagnostics.E1() +} +diagnostics.E2()` + + const expected = `import { diagnostics } from './diagnostics' +if (condition) { + const diagnostics = getDiagnostics() + diagnostics.E1() +} +process.env.NODE_ENV !== 'production' && diagnostics.E2()` + + expectCallSiteTransform(input, expected) }) }) - describe('compound expression wrapping', () => { - it('wraps logical AND with logger on right', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -someCondition && log.E1().warn()` + describe('bare conditional expression wrapping', () => { + it('wraps bare logical AND with diagnostics on right', () => { + const input = `import { diagnostics } from './diagnostics' +someCondition && diagnostics.E1()` + + const expected = `import { diagnostics } from './diagnostics' +process.env.NODE_ENV !== 'production' && someCondition && diagnostics.E1()` + + expectCallSiteTransform(input, expected) + }) + + it('wraps bare logical OR with diagnostics on right', () => { + const input = `import { diagnostics } from './diagnostics' +condition || diagnostics.E1()` + + const expected = `import { diagnostics } from './diagnostics' +process.env.NODE_ENV !== 'production' && (condition || diagnostics.E1())` + + expectCallSiteTransform(input, expected) + }) + + it('wraps bare ternary that reports one of two diagnostics', () => { + const input = `import { diagnostics } from './diagnostics' +condition ? diagnostics.E1() : diagnostics.E2()` + + const expected = `import { diagnostics } from './diagnostics' +process.env.NODE_ENV !== 'production' && (condition ? diagnostics.E1() : diagnostics.E2())` + + expectCallSiteTransform(input, expected) + }) + }) - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -process.env.NODE_ENV !== 'production' && someCondition && log.E1().warn()` + describe('value-passing patterns', () => { + it('does not wrap variable declaration', () => { + const input = `import { diagnostics } from './diagnostics' +const x = diagnostics.E1()` - expectTransform(input, expected) + expectCallSiteUnchanged(input) }) - it('wraps logical OR with logger on left', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -log.E1().warn() || fallback()` + it('does not wrap return statement', () => { + const input = `import { diagnostics } from './diagnostics' +function handler() { + return diagnostics.E1() +}` + + expectCallSiteUnchanged(input) + }) - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -process.env.NODE_ENV !== 'production' && (log.E1().warn() || fallback())` + it('does not wrap ternary returned from a function', () => { + const input = `import { diagnostics } from './diagnostics' +function handler(condition) { + return condition ? diagnostics.E1() : null +}` - expectTransform(input, expected) + expectCallSiteUnchanged(input) }) - it('wraps ternary with logger in consequent', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -condition ? log.E1().warn() : null` + it('does not wrap diagnostics passed as function argument', () => { + const input = `import { diagnostics } from './diagnostics' +fn(diagnostics.E1())` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -process.env.NODE_ENV !== 'production' && (condition ? log.E1().warn() : null)` + expectCallSiteUnchanged(input) + }) + + it('does not wrap ternary passed as function argument', () => { + const input = `import { diagnostics } from './diagnostics' +fn(condition ? diagnostics.E1() : null)` - expectTransform(input, expected) + expectCallSiteUnchanged(input) }) - it('wraps ternary with logger in alternate', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -condition ? null : log.E1().warn()` + it('does not wrap diagnostics passed as method argument', () => { + const input = `import { diagnostics } from './diagnostics' +arr.push(diagnostics.E1())` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -process.env.NODE_ENV !== 'production' && (condition ? null : log.E1().warn())` + expectCallSiteUnchanged(input) + }) - expectTransform(input, expected) + it('does not wrap assignment expression', () => { + const input = `import { diagnostics } from './diagnostics' +let x +x = diagnostics.E1()` + + expectCallSiteUnchanged(input) }) + }) - it('wraps void expression', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -void log.E1().warn()` + describe('throw statements', () => { + it('does not wrap a top-level throw statement', () => { + const input = `import { diagnostics } from './diagnostics' +throw diagnostics.E1()` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -process.env.NODE_ENV !== 'production' && void log.E1().warn()` + expectCallSiteUnchanged(input) + }) + + it('does not wrap a throw with arguments', () => { + const input = `import { diagnostics } from './diagnostics' +throw diagnostics.E1({ src: '/bad.ts' })` - expectTransform(input, expected) + expectCallSiteUnchanged(input) }) - it('wraps await expression', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -async function handler() { - await log.E1().warn() + it('does not wrap a throw inside a function body', () => { + const input = `import { diagnostics } from './diagnostics' +function validate(x) { + if (!x) { + throw diagnostics.E1() + } }` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -async function handler() { - process.env.NODE_ENV !== 'production' && await log.E1().warn() + expectCallSiteUnchanged(input) + }) + + it('does not wrap a throw inside an if block', () => { + const input = `import { diagnostics } from './diagnostics' +if (bad) { + throw diagnostics.E1() }` - expectTransform(input, expected) + expectCallSiteUnchanged(input) }) + }) - it('wraps negation expression', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -!log.E1().warn()` + describe('statement contexts', () => { + it('wraps inside a brace-less if', () => { + const input = `import { diagnostics } from './diagnostics' +if (cond) diagnostics.E1()` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -process.env.NODE_ENV !== 'production' && !log.E1().warn()` + const expected = `import { diagnostics } from './diagnostics' +if (cond) process.env.NODE_ENV !== 'production' && diagnostics.E1()` - expectTransform(input, expected) + expectCallSiteTransform(input, expected) }) - it('wraps sequence expression with logger last', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -;(a(), log.E1().warn())` + it('wraps inside a brace-less else', () => { + const input = `import { diagnostics } from './diagnostics' +if (cond) {} else diagnostics.E1()` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -;process.env.NODE_ENV !== 'production' && (a(), log.E1().warn())` + const expected = `import { diagnostics } from './diagnostics' +if (cond) {} else process.env.NODE_ENV !== 'production' && diagnostics.E1()` - expectTransform(input, expected) + expectCallSiteTransform(input, expected) }) - it('wraps sequence expression with logger first', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -;(log.E1().warn(), a())` + it('wraps inside a for...of loop', () => { + const input = `import { diagnostics } from './diagnostics' +for (const x of arr) { + diagnostics.E1() +}` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -;process.env.NODE_ENV !== 'production' && (log.E1().warn(), a())` + const expected = `import { diagnostics } from './diagnostics' +for (const x of arr) { + process.env.NODE_ENV !== 'production' && diagnostics.E1() +}` - expectTransform(input, expected) + expectCallSiteTransform(input, expected) }) - }) - describe('value-passing patterns', () => { - it('does not wrap variable declaration', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -const x = log.E1().warn()` + it('wraps inside a for...in loop', () => { + const input = `import { diagnostics } from './diagnostics' +for (const k in obj) { + diagnostics.E1() +}` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -const x = log.E1().warn()` + const expected = `import { diagnostics } from './diagnostics' +for (const k in obj) { + process.env.NODE_ENV !== 'production' && diagnostics.E1() +}` - expectTransform(input, expected) + expectCallSiteTransform(input, expected) }) - it('does not wrap return statement', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -function handler() { - return log.E1().warn() + it('wraps inside try/catch/finally blocks', () => { + const input = `import { diagnostics } from './diagnostics' +try { + diagnostics.E1() +} catch (e) { + diagnostics.E2() +} finally { + diagnostics.E3() }` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -function handler() { - return log.E1().warn() + const expected = `import { diagnostics } from './diagnostics' +try { + process.env.NODE_ENV !== 'production' && diagnostics.E1() +} catch (e) { + process.env.NODE_ENV !== 'production' && diagnostics.E2() +} finally { + process.env.NODE_ENV !== 'production' && diagnostics.E3() }` - expectTransform(input, expected) + expectCallSiteTransform(input, expected) }) - it('does not wrap logger passed as function argument', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -fn(log.E1().warn())` + it('wraps inside a switch case', () => { + const input = `import { diagnostics } from './diagnostics' +switch (x) { + case 1: + diagnostics.E1() + break +}` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -fn(log.E1().warn())` + const expected = `import { diagnostics } from './diagnostics' +switch (x) { + case 1: + process.env.NODE_ENV !== 'production' && diagnostics.E1() + break +}` - expectTransform(input, expected) + expectCallSiteTransform(input, expected) }) - it('does not wrap logger passed as method argument', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -arr.push(log.E1().warn())` + it('wraps inside an arrow function block body', () => { + const input = `import { diagnostics } from './diagnostics' +const f = () => { + diagnostics.E1() +}` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -arr.push(log.E1().warn())` + const expected = `import { diagnostics } from './diagnostics' +const f = () => { + process.env.NODE_ENV !== 'production' && diagnostics.E1() +}` - expectTransform(input, expected) + expectCallSiteTransform(input, expected) }) - it('does not wrap assignment expression', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) -let x -x = log.E1().warn()` + it('wraps inside a function expression assigned to a variable', () => { + const input = `import { diagnostics } from './diagnostics' +const f = function () { + diagnostics.E1() +}` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) -let x -x = log.E1().warn()` + const expected = `import { diagnostics } from './diagnostics' +const f = function () { + process.env.NODE_ENV !== 'production' && diagnostics.E1() +}` + + expectCallSiteTransform(input, expected) + }) + + it('wraps inside a class method', () => { + const input = `import { diagnostics } from './diagnostics' +class A { + m() { + diagnostics.E1() + } +}` + + const expected = `import { diagnostics } from './diagnostics' +class A { + m() { + process.env.NODE_ENV !== 'production' && diagnostics.E1() + } +}` - expectTransform(input, expected) + expectCallSiteTransform(input, expected) + }) + + it('does not wrap an arrow with an expression body', () => { + const input = `import { diagnostics } from './diagnostics' +const f = () => diagnostics.E1()` + + expectCallSiteUnchanged(input) }) }) - describe('does not transform non-logging code', () => { + describe('does not transform non-diagnostic code', () => { it('does not wrap unrelated expression statements', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) + const input = `import { diagnostics } from './diagnostics' console.log('hello') -log.E1().warn()` +diagnostics.E1()` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) + const expected = `import { diagnostics } from './diagnostics' console.log('hello') -process.env.NODE_ENV !== 'production' && log.E1().warn()` +process.env.NODE_ENV !== 'production' && diagnostics.E1()` - expectTransform(input, expected) + expectCallSiteTransform(input, expected) }) - it('does not add PURE to non-imported function calls', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({}) + it('does not add PURE to non-imported function calls in call-site files', () => { + const input = `import { diagnostics } from './diagnostics' const other = someOtherFunction() -log.E1().warn()` +diagnostics.E1()` - const expected = `import { createLogger } from 'nostics' -const log = /*#__PURE__*/ createLogger({}) + const expected = `import { diagnostics } from './diagnostics' const other = someOtherFunction() -process.env.NODE_ENV !== 'production' && log.E1().warn()` +process.env.NODE_ENV !== 'production' && diagnostics.E1()` - expectTransform(input, expected) + expectCallSiteTransform(input, expected) }) }) - describe('custom package name', () => { - it('supports custom package name', () => { - const input = `import { createLogger } from 'my-custom-sdk' -const log = createLogger({}) -log.E1().warn()` - - const expected = `import { createLogger } from 'my-custom-sdk' -const log = /*#__PURE__*/ createLogger({}) -process.env.NODE_ENV !== 'production' && log.E1().warn()` + describe('source maps', () => { + it('produces a source map for definition files', () => { + const input = `import { defineDiagnostics } from 'nostics' +export const diagnostics = defineDiagnostics({ codes: {} })` - const result = transform(input, 'test.ts', { packageName: 'my-custom-sdk' }) + const result = transform(input, 'test.ts') expect(result).toBeDefined() - expect(result!.code).toBe(expected) + expect(result!.map).toBeDefined() + expect(result!.map.mappings).toBeTruthy() }) - }) - describe('source maps', () => { - it('produces a source map', () => { - const input = `import { createLogger } from 'nostics' -const log = createLogger({})` + it('produces a source map for call-site files', () => { + const input = `import { diagnostics } from './diagnostics' +diagnostics.E1()` - const result = transform(input, 'test.ts') + const result = transform(input, CALLSITE_ID, undefined, new Map()) expect(result).toBeDefined() expect(result!.map).toBeDefined() expect(result!.map.mappings).toBeTruthy() diff --git a/src/unplugin/transform.ts b/src/unplugin/transform.ts index 90cbb19..650c779 100644 --- a/src/unplugin/transform.ts +++ b/src/unplugin/transform.ts @@ -37,20 +37,23 @@ export function transform( options?: TransformOptions, trackedExportsMap?: TrackedExportsMap, ): TransformResult | undefined { + const CONDITION = 'process.env.NODE_ENV !== \'production\'' const packageName = options?.packageName ?? 'nostics' const result = parseSync(id, code) const ast = result.program - // Step 1: Find direct imports from the package - const importedNames = new Map() // localName -> importedName + // Step 1: Find direct defineDiagnostics imports from the package + const defineDiagnosticsImports = new Set() for (const node of ast.body) { if (node.type === 'ImportDeclaration' && node.source.value === packageName) { for (const spec of node.specifiers) { if (spec.type === 'ImportSpecifier') { const importedName = spec.imported.type === 'Identifier' ? spec.imported.name : spec.imported.value - importedNames.set(spec.local.name, importedName) + if (importedName === 'defineDiagnostics') { + defineDiagnosticsImports.add(spec.local.name) + } } } } @@ -91,14 +94,19 @@ export function transform( } } - if (importedNames.size === 0 && crossFileTracked.size === 0) + if (defineDiagnosticsImports.size === 0 && crossFileTracked.size === 0) return undefined const s = new MagicString(code) const trackedVars = new Set(crossFileTracked) - // Step 3: Walk all statements recursively - walkStatements(ast.body, s, importedNames, trackedVars) + // Step 3a: track top-level variables derived from defineDiagnostics calls and + // mark the calls as pure. + trackTopLevelDefinitions(ast.body, s, defineDiagnosticsImports, trackedVars) + + // Step 3b: wrap expression statements that use a tracked variable while + // respecting lexical shadowing in nested scopes. + wrapTrackedExpressionStatements(ast, s, trackedVars, new Set(), CONDITION) if (!s.hasChanged()) return undefined @@ -129,8 +137,6 @@ export function transform( } } -const CONDITION = 'process.env.NODE_ENV !== \'production\'' - /** * Check if an expression has lower precedence than `&&` and needs inner parens * when used as the right-hand side of `guard && expr`. @@ -200,19 +206,23 @@ function analyzeModule( const result = parseSync(filePath, source) const ast = result.program - // Find imports from the package - const importedNames = new Set() + // Find defineDiagnostics imports from the package + const defineDiagnosticsImports = new Set() for (const node of ast.body) { if (node.type === 'ImportDeclaration' && node.source.value === packageName) { for (const spec of node.specifiers) { if (spec.type === 'ImportSpecifier') { - importedNames.add(spec.local.name) + const importedName + = spec.imported.type === 'Identifier' ? spec.imported.name : spec.imported.value + if (importedName === 'defineDiagnostics') { + defineDiagnosticsImports.add(spec.local.name) + } } } } } - if (importedNames.size === 0) + if (defineDiagnosticsImports.size === 0) return // Find exported variables assigned from imported function calls @@ -226,7 +236,7 @@ function analyzeModule( if ( decl.init?.type === 'CallExpression' && decl.init.callee?.type === 'Identifier' - && importedNames.has(decl.init.callee.name) + && defineDiagnosticsImports.has(decl.init.callee.name) && decl.id?.type === 'Identifier' ) { trackedExports.add(decl.id.name) @@ -240,73 +250,159 @@ function analyzeModule( } } -function walkStatements( +function trackTopLevelDefinitions( body: any[], s: MagicString, - importedNames: Map, + defineDiagnosticsImports: Set, trackedVars: Set, ): void { - for (const stmt of body) { - // Variable declaration: const x = importedFn(...) - if (stmt.type === 'VariableDeclaration') { - for (const decl of stmt.declarations) { - if ( - decl.init?.type === 'CallExpression' - && decl.init.callee?.type === 'Identifier' - && importedNames.has(decl.init.callee.name) - && decl.id?.type === 'Identifier' - ) { - // Track the variable - trackedVars.add(decl.id.name) - // Add /*#__PURE__*/ before the call expression - s.appendLeft(decl.init.start, '/*#__PURE__*/ ') - } - } + for (const node of body) { + if (node.type === 'VariableDeclaration') { + trackVariableDeclaration(node, s, defineDiagnosticsImports, trackedVars) + } + else if ( + node.type === 'ExportNamedDeclaration' + && node.declaration?.type === 'VariableDeclaration' + ) { + trackVariableDeclaration(node.declaration, s, defineDiagnosticsImports, trackedVars) } + } +} - // Expression statement using a tracked variable - if (stmt.type === 'ExpressionStatement') { - if (expressionUsesTrackedVar(stmt.expression, trackedVars)) { - const needsParens = expressionNeedsParens(stmt.expression) - if (needsParens) { - s.appendLeft(stmt.expression.start, `${CONDITION} && (`) - s.appendRight(stmt.expression.end, `)`) - } - else { - s.appendLeft(stmt.expression.start, `${CONDITION} && `) - } - } +function trackVariableDeclaration( + node: any, + s: MagicString, + defineDiagnosticsImports: Set, + trackedVars: Set, +): void { + for (const decl of node.declarations) { + if ( + decl.init?.type === 'CallExpression' + && decl.init.callee?.type === 'Identifier' + && defineDiagnosticsImports.has(decl.init.callee.name) + && decl.id?.type === 'Identifier' + ) { + trackedVars.add(decl.id.name) + s.appendLeft(decl.init.start, '/*#__PURE__*/ ') } + } +} + +function wrapTrackedExpressionStatements( + node: any, + s: MagicString, + trackedVars: Set, + shadowedVars: Set, + condition: string, +): void { + if (!node || typeof node !== 'object') + return - // Recurse into block-containing statements - if (stmt.type === 'BlockStatement' || stmt.type === 'Program') { - walkStatements(stmt.body, s, importedNames, trackedVars) + if (Array.isArray(node)) { + for (const child of node) { + wrapTrackedExpressionStatements(child, s, trackedVars, shadowedVars, condition) } - if (stmt.type === 'IfStatement') { - if (stmt.consequent?.type === 'BlockStatement') { - walkStatements(stmt.consequent.body, s, importedNames, trackedVars) - } - if (stmt.alternate?.type === 'BlockStatement') { - walkStatements(stmt.alternate.body, s, importedNames, trackedVars) - } + return + } + + if (node.type === 'Program') { + wrapTrackedExpressionStatements(node.body, s, trackedVars, shadowedVars, condition) + return + } + + if (node.type === 'BlockStatement') { + const blockShadowedVars = new Set(shadowedVars) + for (const stmt of node.body) { + collectStatementBindingNames(stmt, blockShadowedVars) } - if ( - stmt.type === 'ForStatement' - || stmt.type === 'WhileStatement' - || stmt.type === 'DoWhileStatement' - ) { - if (stmt.body?.type === 'BlockStatement') { - walkStatements(stmt.body.body, s, importedNames, trackedVars) + wrapTrackedExpressionStatements(node.body, s, trackedVars, blockShadowedVars, condition) + return + } + + if ( + node.type === 'FunctionDeclaration' + || node.type === 'FunctionExpression' + || node.type === 'ArrowFunctionExpression' + ) { + if (node.body?.type === 'BlockStatement') { + const functionShadowedVars = new Set(shadowedVars) + for (const param of node.params ?? []) { + collectPatternNames(param, functionShadowedVars) } + wrapTrackedExpressionStatements( + node.body, + s, + trackedVars, + functionShadowedVars, + condition, + ) } - if (stmt.type === 'FunctionDeclaration' || stmt.type === 'ArrowFunctionExpression') { - if (stmt.body?.type === 'BlockStatement') { - walkStatements(stmt.body.body, s, importedNames, trackedVars) - } + return + } + + if (node.type === 'CatchClause') { + const catchShadowedVars = new Set(shadowedVars) + collectPatternNames(node.param, catchShadowedVars) + wrapTrackedExpressionStatements(node.body, s, trackedVars, catchShadowedVars, condition) + return + } + + if ( + node.type === 'ExpressionStatement' + && expressionUsesTrackedVar(node.expression, trackedVars, shadowedVars) + ) { + if (expressionNeedsParens(node.expression)) { + s.appendLeft(node.expression.start, `${condition} && (`) + s.appendRight(node.expression.end, `)`) } - // Handle ExportNamedDeclaration wrapping a VariableDeclaration - if (stmt.type === 'ExportNamedDeclaration' && stmt.declaration) { - walkStatements([stmt.declaration], s, importedNames, trackedVars) + else { + s.appendLeft(node.expression.start, `${condition} && `) + } + return + } + + for (const key in node) { + if (key === 'type' || key === 'start' || key === 'end') + continue + wrapTrackedExpressionStatements(node[key], s, trackedVars, shadowedVars, condition) + } +} + +function collectStatementBindingNames(node: any, names: Set): void { + if (!node) + return + + if (node.type === 'VariableDeclaration') { + for (const decl of node.declarations) { + collectPatternNames(decl.id, names) + } + } + else if (node.type === 'FunctionDeclaration' || node.type === 'ClassDeclaration') { + collectPatternNames(node.id, names) + } +} + +function collectPatternNames(node: any, names: Set): void { + if (!node) + return + + if (node.type === 'Identifier') { + names.add(node.name) + } + else if (node.type === 'AssignmentPattern') { + collectPatternNames(node.left, names) + } + else if (node.type === 'RestElement') { + collectPatternNames(node.argument, names) + } + else if (node.type === 'ArrayPattern') { + for (const element of node.elements) { + collectPatternNames(element, names) + } + } + else if (node.type === 'ObjectPattern') { + for (const property of node.properties) { + collectPatternNames(property.value ?? property.argument, names) } } } @@ -314,59 +410,64 @@ function walkStatements( /** * Check if an expression references a tracked variable as the root of a member/call chain. */ -function expressionUsesTrackedVar(node: any, trackedVars: Set): boolean { +function expressionUsesTrackedVar( + node: any, + trackedVars: Set, + shadowedVars: Set, +): boolean { if (!node) return false // Direct identifier reference if (node.type === 'Identifier') { - return trackedVars.has(node.name) + return trackedVars.has(node.name) && !shadowedVars.has(node.name) } // Member expression: check the object (root of the chain) if (node.type === 'MemberExpression') { - return expressionUsesTrackedVar(node.object, trackedVars) + return expressionUsesTrackedVar(node.object, trackedVars, shadowedVars) } // Call expression: check the callee if (node.type === 'CallExpression') { - return expressionUsesTrackedVar(node.callee, trackedVars) + return expressionUsesTrackedVar(node.callee, trackedVars, shadowedVars) } // Logical expression: check either side if (node.type === 'LogicalExpression') { return ( - expressionUsesTrackedVar(node.left, trackedVars) - || expressionUsesTrackedVar(node.right, trackedVars) + expressionUsesTrackedVar(node.left, trackedVars, shadowedVars) + || expressionUsesTrackedVar(node.right, trackedVars, shadowedVars) ) } // Conditional (ternary): check consequent or alternate if (node.type === 'ConditionalExpression') { return ( - expressionUsesTrackedVar(node.consequent, trackedVars) - || expressionUsesTrackedVar(node.alternate, trackedVars) + expressionUsesTrackedVar(node.consequent, trackedVars, shadowedVars) + || expressionUsesTrackedVar(node.alternate, trackedVars, shadowedVars) ) } // Unary expression: check argument if (node.type === 'UnaryExpression') { - return expressionUsesTrackedVar(node.argument, trackedVars) + return expressionUsesTrackedVar(node.argument, trackedVars, shadowedVars) } // Await expression: check argument if (node.type === 'AwaitExpression') { - return expressionUsesTrackedVar(node.argument, trackedVars) + return expressionUsesTrackedVar(node.argument, trackedVars, shadowedVars) } // Sequence expression: check any element if (node.type === 'SequenceExpression') { - return node.expressions.some((expr: any) => expressionUsesTrackedVar(expr, trackedVars)) + return node.expressions.some((expr: any) => + expressionUsesTrackedVar(expr, trackedVars, shadowedVars)) } // Parenthesized expression: unwrap if (node.type === 'ParenthesizedExpression') { - return expressionUsesTrackedVar(node.expression, trackedVars) + return expressionUsesTrackedVar(node.expression, trackedVars, shadowedVars) } return false diff --git a/src/unplugin/tree-shake.test.ts b/src/unplugin/tree-shake.test.ts index 93fc7cf..e4ccf4b 100644 --- a/src/unplugin/tree-shake.test.ts +++ b/src/unplugin/tree-shake.test.ts @@ -1,16 +1,21 @@ +import type { TrackedExportsMap } from './transform' +import { readFileSync } from 'node:fs' +import { dirname, join } from 'node:path' import { build } from 'esbuild' import { describe, expect, it } from 'vitest' import { transform } from './transform' async function bundleProduction(input: string): Promise { + const id = join(import.meta.dirname, '../../demo-lib/src/entry.ts') + const trackedExportsMap: TrackedExportsMap = new Map() // First, apply the plugin transform - const transformed = transform(input, 'entry.ts') + const transformed = transform(input, id, undefined, trackedExportsMap) const code = transformed ? transformed.code : input const result = await build({ stdin: { contents: code, - resolveDir: import.meta.dirname, + resolveDir: dirname(id), loader: 'ts', }, bundle: true, @@ -32,13 +37,27 @@ async function bundleProduction(input: string): Promise { build.onLoad({ filter: /.*/, namespace: 'nostics-stub' }, () => ({ contents: ` export function defineDiagnostics(opts) { return opts } - export function createLogger(opts) { return opts } + export function reporterLog() {} + export function devReporter() {} export const consoleReporter = { report() {} } `, loader: 'js', })) }, }, + { + name: 'nostics-transform', + setup(build) { + build.onLoad({ filter: /demo-lib\/src\/.*\.ts$/ }, (args) => { + const source = readFileSync(args.path, 'utf-8') + const transformed = transform(source, args.path, undefined, trackedExportsMap) + return { + contents: transformed?.code ?? source, + loader: 'ts', + } + }) + }, + }, ], }) @@ -46,45 +65,31 @@ async function bundleProduction(input: string): Promise { } describe('tree-shake integration', () => { - it('eliminates all logging code in production', async () => { + it('eliminates all diagnostic call code in production', async () => { const input = ` -import { defineDiagnostics, createLogger } from 'nostics' +import { diagnostics } from './diagnostics' -const diags = defineDiagnostics({ - prefix: 'TEST', - codes: { - E001: { message: 'Test error' }, - E002: { message: (p: { file: string }) => \`Bad file \${p.file}\` }, - }, -}) - -const log = createLogger({ diagnostics: [diags] }) - -log.E001().warn() -log.E002({ file: '/bad.ts' }).error() +diagnostics.MATH_E001() +diagnostics.MATH_W001({ n: -1 }) ` const output = await bundleProduction(input) // After tree-shaking and minification, the output should be nothing expect(output.trim()).toBe('') }) - it('eliminates logging inside functions', async () => { + it('eliminates diagnostic calls inside functions', async () => { const input = ` -import { defineDiagnostics, createLogger } from 'nostics' - -const diags = defineDiagnostics({ prefix: 'T', codes: { E1: { message: 'x' } } }) -const log = createLogger({ diagnostics: [diags] }) +import { diagnostics } from './diagnostics' export function handler() { - log.E1().warn() + diagnostics.MATH_E001() return 'ok' } ` const output = await bundleProduction(input) - // The handler function should remain but logging should be gone - expect(output).toMatchInlineSnapshot(` - "function t(){return"ok"}export{t as handler}; - " - `) + // The handler function should remain but the diagnostic call should be gone. + expect(output.trim()).toMatch(/^function \w\(\)\{return"ok"\}export\{\w as handler\};$/) + expect(output).not.toContain('E1') + expect(output).not.toContain('defineDiagnostics') }) }) From 41d8a0ff7a70c4523b111fc49088d9b0cc2d61cc Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Tue, 2 Jun 2026 13:27:23 +0200 Subject: [PATCH 2/3] refactor: move const out + quotes --- src/unplugin/transform.test.ts | 56 +++++++++++++++++----------------- src/unplugin/transform.ts | 14 +++------ 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/src/unplugin/transform.test.ts b/src/unplugin/transform.test.ts index d20dc9a..b3f4e48 100644 --- a/src/unplugin/transform.test.ts +++ b/src/unplugin/transform.test.ts @@ -91,7 +91,7 @@ export function run() { const expected = `import { diagnostics } from './diagnostics' export function run() { - process.env.NODE_ENV !== 'production' && diagnostics.E1() + process.env.NODE_ENV !== "production" && diagnostics.E1() }` expectCallSiteTransform(input, expected) @@ -113,7 +113,7 @@ export const diagnostics = defineDiagnostics({ codes: {} })`, diagnostics.E1()` const expected = `import { diagnostics } from './diagnostics' -process.env.NODE_ENV !== 'production' && diagnostics.E1()` +process.env.NODE_ENV !== "production" && diagnostics.E1()` const result = transform(input, CALLSITE_ID, undefined, trackedExportsMap) expect(result).toBeDefined() @@ -125,7 +125,7 @@ process.env.NODE_ENV !== 'production' && diagnostics.E1()` diag.E1()` const expected = `import { diagnostics as diag } from './diagnostics' -process.env.NODE_ENV !== 'production' && diag.E1()` +process.env.NODE_ENV !== "production" && diag.E1()` expectCallSiteTransform(input, expected) }) @@ -144,7 +144,7 @@ diagnostics.E1()` diagnostics.E1()` const expected = `import { diagnostics } from './diagnostics' -process.env.NODE_ENV !== 'production' && diagnostics.E1()` +process.env.NODE_ENV !== "production" && diagnostics.E1()` expectCallSiteTransform(input, expected) }) @@ -154,7 +154,7 @@ process.env.NODE_ENV !== 'production' && diagnostics.E1()` diagnostics.B2011({ src: '/bad.ts' })` const expected = `import { diagnostics } from './diagnostics' -process.env.NODE_ENV !== 'production' && diagnostics.B2011({ src: '/bad.ts' })` +process.env.NODE_ENV !== "production" && diagnostics.B2011({ src: '/bad.ts' })` expectCallSiteTransform(input, expected) }) @@ -166,9 +166,9 @@ diagnostics.E2() diagnostics.E3()` const expected = `import { diagnostics } from './diagnostics' -process.env.NODE_ENV !== 'production' && diagnostics.E1() -process.env.NODE_ENV !== 'production' && diagnostics.E2() -process.env.NODE_ENV !== 'production' && diagnostics.E3()` +process.env.NODE_ENV !== "production" && diagnostics.E1() +process.env.NODE_ENV !== "production" && diagnostics.E2() +process.env.NODE_ENV !== "production" && diagnostics.E3()` expectCallSiteTransform(input, expected) }) @@ -183,7 +183,7 @@ function handler() { const expected = `import { diagnostics } from './diagnostics' function handler() { - process.env.NODE_ENV !== 'production' && diagnostics.E1() + process.env.NODE_ENV !== "production" && diagnostics.E1() }` expectCallSiteTransform(input, expected) @@ -197,7 +197,7 @@ if (condition) { const expected = `import { diagnostics } from './diagnostics' if (condition) { - process.env.NODE_ENV !== 'production' && diagnostics.E1() + process.env.NODE_ENV !== "production" && diagnostics.E1() }` expectCallSiteTransform(input, expected) @@ -214,7 +214,7 @@ diagnostics.E2()` function setup(diagnostics) { diagnostics.E1() } -process.env.NODE_ENV !== 'production' && diagnostics.E2()` +process.env.NODE_ENV !== "production" && diagnostics.E2()` expectCallSiteTransform(input, expected) }) @@ -232,7 +232,7 @@ if (condition) { const diagnostics = getDiagnostics() diagnostics.E1() } -process.env.NODE_ENV !== 'production' && diagnostics.E2()` +process.env.NODE_ENV !== "production" && diagnostics.E2()` expectCallSiteTransform(input, expected) }) @@ -244,7 +244,7 @@ process.env.NODE_ENV !== 'production' && diagnostics.E2()` someCondition && diagnostics.E1()` const expected = `import { diagnostics } from './diagnostics' -process.env.NODE_ENV !== 'production' && someCondition && diagnostics.E1()` +process.env.NODE_ENV !== "production" && someCondition && diagnostics.E1()` expectCallSiteTransform(input, expected) }) @@ -254,7 +254,7 @@ process.env.NODE_ENV !== 'production' && someCondition && diagnostics.E1()` condition || diagnostics.E1()` const expected = `import { diagnostics } from './diagnostics' -process.env.NODE_ENV !== 'production' && (condition || diagnostics.E1())` +process.env.NODE_ENV !== "production" && (condition || diagnostics.E1())` expectCallSiteTransform(input, expected) }) @@ -264,7 +264,7 @@ process.env.NODE_ENV !== 'production' && (condition || diagnostics.E1())` condition ? diagnostics.E1() : diagnostics.E2()` const expected = `import { diagnostics } from './diagnostics' -process.env.NODE_ENV !== 'production' && (condition ? diagnostics.E1() : diagnostics.E2())` +process.env.NODE_ENV !== "production" && (condition ? diagnostics.E1() : diagnostics.E2())` expectCallSiteTransform(input, expected) }) @@ -368,7 +368,7 @@ if (bad) { if (cond) diagnostics.E1()` const expected = `import { diagnostics } from './diagnostics' -if (cond) process.env.NODE_ENV !== 'production' && diagnostics.E1()` +if (cond) process.env.NODE_ENV !== "production" && diagnostics.E1()` expectCallSiteTransform(input, expected) }) @@ -378,7 +378,7 @@ if (cond) process.env.NODE_ENV !== 'production' && diagnostics.E1()` if (cond) {} else diagnostics.E1()` const expected = `import { diagnostics } from './diagnostics' -if (cond) {} else process.env.NODE_ENV !== 'production' && diagnostics.E1()` +if (cond) {} else process.env.NODE_ENV !== "production" && diagnostics.E1()` expectCallSiteTransform(input, expected) }) @@ -391,7 +391,7 @@ for (const x of arr) { const expected = `import { diagnostics } from './diagnostics' for (const x of arr) { - process.env.NODE_ENV !== 'production' && diagnostics.E1() + process.env.NODE_ENV !== "production" && diagnostics.E1() }` expectCallSiteTransform(input, expected) @@ -405,7 +405,7 @@ for (const k in obj) { const expected = `import { diagnostics } from './diagnostics' for (const k in obj) { - process.env.NODE_ENV !== 'production' && diagnostics.E1() + process.env.NODE_ENV !== "production" && diagnostics.E1() }` expectCallSiteTransform(input, expected) @@ -423,11 +423,11 @@ try { const expected = `import { diagnostics } from './diagnostics' try { - process.env.NODE_ENV !== 'production' && diagnostics.E1() + process.env.NODE_ENV !== "production" && diagnostics.E1() } catch (e) { - process.env.NODE_ENV !== 'production' && diagnostics.E2() + process.env.NODE_ENV !== "production" && diagnostics.E2() } finally { - process.env.NODE_ENV !== 'production' && diagnostics.E3() + process.env.NODE_ENV !== "production" && diagnostics.E3() }` expectCallSiteTransform(input, expected) @@ -444,7 +444,7 @@ switch (x) { const expected = `import { diagnostics } from './diagnostics' switch (x) { case 1: - process.env.NODE_ENV !== 'production' && diagnostics.E1() + process.env.NODE_ENV !== "production" && diagnostics.E1() break }` @@ -459,7 +459,7 @@ const f = () => { const expected = `import { diagnostics } from './diagnostics' const f = () => { - process.env.NODE_ENV !== 'production' && diagnostics.E1() + process.env.NODE_ENV !== "production" && diagnostics.E1() }` expectCallSiteTransform(input, expected) @@ -473,7 +473,7 @@ const f = function () { const expected = `import { diagnostics } from './diagnostics' const f = function () { - process.env.NODE_ENV !== 'production' && diagnostics.E1() + process.env.NODE_ENV !== "production" && diagnostics.E1() }` expectCallSiteTransform(input, expected) @@ -490,7 +490,7 @@ class A { const expected = `import { diagnostics } from './diagnostics' class A { m() { - process.env.NODE_ENV !== 'production' && diagnostics.E1() + process.env.NODE_ENV !== "production" && diagnostics.E1() } }` @@ -513,7 +513,7 @@ diagnostics.E1()` const expected = `import { diagnostics } from './diagnostics' console.log('hello') -process.env.NODE_ENV !== 'production' && diagnostics.E1()` +process.env.NODE_ENV !== "production" && diagnostics.E1()` expectCallSiteTransform(input, expected) }) @@ -525,7 +525,7 @@ diagnostics.E1()` const expected = `import { diagnostics } from './diagnostics' const other = someOtherFunction() -process.env.NODE_ENV !== 'production' && diagnostics.E1()` +process.env.NODE_ENV !== "production" && diagnostics.E1()` expectCallSiteTransform(input, expected) }) diff --git a/src/unplugin/transform.ts b/src/unplugin/transform.ts index 650c779..6930022 100644 --- a/src/unplugin/transform.ts +++ b/src/unplugin/transform.ts @@ -22,6 +22,8 @@ export interface TransformOptions { */ export type TrackedExportsMap = Map> +const CONDITION = 'process.env.NODE_ENV !== "production"' + /** * Transforms code that imports from `nostics`: * - Adds `\/*#__PURE__*\/` to `defineDiagnostics()` call expressions @@ -37,7 +39,6 @@ export function transform( options?: TransformOptions, trackedExportsMap?: TrackedExportsMap, ): TransformResult | undefined { - const CONDITION = 'process.env.NODE_ENV !== \'production\'' const packageName = options?.packageName ?? 'nostics' const result = parseSync(id, code) @@ -329,13 +330,7 @@ function wrapTrackedExpressionStatements( for (const param of node.params ?? []) { collectPatternNames(param, functionShadowedVars) } - wrapTrackedExpressionStatements( - node.body, - s, - trackedVars, - functionShadowedVars, - condition, - ) + wrapTrackedExpressionStatements(node.body, s, trackedVars, functionShadowedVars, condition) } return } @@ -462,7 +457,8 @@ function expressionUsesTrackedVar( // Sequence expression: check any element if (node.type === 'SequenceExpression') { return node.expressions.some((expr: any) => - expressionUsesTrackedVar(expr, trackedVars, shadowedVars)) + expressionUsesTrackedVar(expr, trackedVars, shadowedVars), + ) } // Parenthesized expression: unwrap From 493f752d994304a4258fa2b7a1413142f1e165e0 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Tue, 2 Jun 2026 13:38:09 +0200 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20force=20text=20presentation=20for=20?= =?UTF-8?q?=E2=96=B6=20connectors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/diagnostic.test.ts | 4 ++-- src/diagnostic.ts | 2 +- src/formatters/ansi.test.ts | 8 ++++---- src/formatters/ansi.ts | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/diagnostic.test.ts b/src/diagnostic.test.ts index 8b9d2b9..3978cf4 100644 --- a/src/diagnostic.test.ts +++ b/src/diagnostic.test.ts @@ -217,7 +217,7 @@ describe('built-in reporters', () => { }) errs.NUXT_E033({ sources: ['a.ts:1:1', 'b.ts:2:2'] }) expect( - '[NUXT_E033] boom\n├▶ fix: restart it\n╰▶ sources: a.ts:1:1, b.ts:2:2', + '[NUXT_E033] boom\n├▶︎ fix: restart it\n╰▶︎ sources: a.ts:1:1, b.ts:2:2', ).toHaveBeenErrored() }) @@ -239,7 +239,7 @@ describe('built-in reporters', () => { docsBase: 'https://example.com/errors', codes: { X: { why: 'boom' } }, }) - expect(formatDiagnostic(errs.X())).toBe('[X] boom\n╰▶ see: https://example.com/errors/x') + expect(formatDiagnostic(errs.X())).toBe('[X] boom\n╰▶︎ see: https://example.com/errors/x') }) it('reporterWithPriority includes the code and the priority value', () => { diff --git a/src/diagnostic.ts b/src/diagnostic.ts index b8dffb4..dac06d8 100644 --- a/src/diagnostic.ts +++ b/src/diagnostic.ts @@ -127,7 +127,7 @@ export function formatDiagnostic(diagnostic: Diagnostic): string { } const lines = details.map((detail, i) => { - const connector = i < details.length - 1 ? '├▶' : '╰▶' + const connector = i < details.length - 1 ? '├▶\uFE0E' : '╰▶\uFE0E' return `${connector} ${detail}` }) diff --git a/src/formatters/ansi.test.ts b/src/formatters/ansi.test.ts index 98b235d..171c81b 100644 --- a/src/formatters/ansi.test.ts +++ b/src/formatters/ansi.test.ts @@ -33,9 +33,9 @@ describe('ansiFormatter', () => { const d = diagnostics.E1({ sources: ['a.ts:1:1', 'b.ts:2:2'] }) expect(format(d)).toMatchInlineSnapshot(` "[E1] broken - ├▶ fix: do x - ├▶ sources: a.ts:1:1, b.ts:2:2 - ╰▶ see: https://docs.test/e1" + ├▶︎ fix: do x + ├▶︎ sources: a.ts:1:1, b.ts:2:2 + ╰▶︎ see: https://docs.test/e1" `) }) @@ -44,7 +44,7 @@ describe('ansiFormatter', () => { const d = diagnostics.E1() expect(format(d)).toMatchInlineSnapshot(` "[E1] broken - ╰▶ fix: do x" + ╰▶︎ fix: do x" `) }) }) diff --git a/src/formatters/ansi.ts b/src/formatters/ansi.ts index 158ed17..e062e83 100644 --- a/src/formatters/ansi.ts +++ b/src/formatters/ansi.ts @@ -26,7 +26,7 @@ export function ansiFormatter(colors: Colors): (d: Diagnostic) => string { return header const lines = details.map((detail, i) => { - const connector = colors.dim(i < details.length - 1 ? '├▶' : '╰▶') + const connector = colors.dim(i < details.length - 1 ? '├▶\uFE0E' : '╰▶\uFE0E') return `${connector} ${detail}` }) return [header, ...lines].join('\n')