From bb2b764276e85eb9a9bb42d3221401a74fc8dc40 Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Sat, 25 Jul 2026 17:23:11 -0400 Subject: [PATCH] fix: format the project's ts with tsv and fix resulting formatting bugs --- CLAUDE.md | 24 +- benches/js/bench.ts | 210 +-- benches/js/compose_reports.ts | 36 +- benches/js/conformance.ts | 14 +- benches/js/corpus_audit.ts | 41 +- benches/js/corpus_compare_format.ts | 138 +- benches/js/corpus_compare_parse.ts | 151 +- benches/js/diagnostics/biome_oxfmt_diff.ts | 2 +- benches/js/diagnostics/corpus_stats.ts | 109 +- benches/js/diagnostics/no_locations_parity.ts | 32 +- .../diagnostics/reconstruct_vs_materialize.ts | 29 +- benches/js/diagnostics/skip_triage.ts | 69 +- .../js/diagnostics/svelte_fixtures_compare.ts | 4 +- .../js/diagnostics/svelte_reject_harvest.ts | 15 +- .../js/diagnostics/svelte_styles_harvest.ts | 6 +- benches/js/diagnostics/test262_compare.ts | 31 +- benches/js/diagnostics/ts_fixtures_compare.ts | 4 +- benches/js/diagnostics/ts_repo_compare.ts | 48 +- benches/js/diagnostics/wasm_format_probe.ts | 56 +- benches/js/diagnostics/wasm_json_probe.ts | 26 +- benches/js/diagnostics/wasm_memory_probe.ts | 38 +- benches/js/diagnostics/wpt_css_harvest.ts | 25 +- benches/js/divergence_audit.ts | 9 +- benches/js/harvest_test262.ts | 21 +- benches/js/idempotency_sweep.ts | 16 +- benches/js/install_deps.ts | 4 +- benches/js/lib/binary_sizes.ts | 120 +- benches/js/lib/biome.ts | 18 +- benches/js/lib/canonical.ts | 39 +- benches/js/lib/canonical_test.ts | 101 +- benches/js/lib/check_artifact_freshness.ts | 25 +- benches/js/lib/check_node_modules.ts | 10 +- benches/js/lib/compare_cli.ts | 33 +- benches/js/lib/corpus.ts | 88 +- benches/js/lib/corpus_repos.ts | 14 +- benches/js/lib/diff.ts | 18 +- benches/js/lib/divergence/expected_errors.ts | 37 +- .../js/lib/divergence/expected_errors_test.ts | 12 +- benches/js/lib/divergence/fixture_cases.ts | 24 +- .../lib/divergence/fixture_coverage_test.ts | 75 +- benches/js/lib/divergence/mod.ts | 6 +- benches/js/lib/divergence/patterns.ts | 442 ++--- benches/js/lib/divergence/patterns_test.ts | 1040 ++++++----- benches/js/lib/divergence/safety.ts | 19 +- benches/js/lib/divergence/safety_test.ts | 23 +- benches/js/lib/divergence/validation.ts | 47 +- benches/js/lib/dprint.ts | 4 +- benches/js/lib/ffi.ts | 84 +- benches/js/lib/fixtures_gate.ts | 82 +- benches/js/lib/gate_counts.ts | 17 +- benches/js/lib/harvest_stamp.ts | 7 +- benches/js/lib/implementations.ts | 57 +- benches/js/lib/napi.ts | 12 +- benches/js/lib/oxc.ts | 17 +- benches/js/lib/oxc_wasm.ts | 18 +- benches/js/lib/parse_sanctions.ts | 5 +- benches/js/lib/perf_omit.ts | 17 +- benches/js/lib/prettier_cache.ts | 4 +- benches/js/lib/report.ts | 202 ++- benches/js/lib/runtime.ts | 8 +- benches/js/lib/types.ts | 14 +- benches/js/lib/versions.ts | 20 +- benches/js/lib/wasm.ts | 16 +- benches/js/smoke.ts | 40 +- .../src/cli/commands/gap_audit_known.txt | 12 +- crates/tsv_debug/src/deno/sidecar.ts | 84 +- crates/tsv_lang/src/source_scan.rs | 129 +- .../tsv_ts/src/parser/expression_lookahead.rs | 319 +++- crates/tsv_ts/src/parser/types.rs | 6 +- crates/tsv_ts/src/printer/calls/chain_args.rs | 19 + crates/tsv_ts/src/printer/chain/adapter.rs | 16 - crates/tsv_ts/src/printer/chain/printing.rs | 43 +- crates/tsv_ts/src/printer/expressions/mod.rs | 109 +- crates/tsv_ts/src/printer/mod.rs | 16 +- crates/tsv_wasm/npm/cli.js | 72 +- crates/tsv_wasm/npm/locations.d.ts | 6 +- crates/tsv_wasm/npm/locations.js | 6 +- crates/tsv_wasm/types/tsv_ast.d.ts | 16 +- deno.json | 3 +- docs/audits.md | 53 + docs/comments.md | 2 + docs/conformance_prettier.md | 3 + scripts/check_ast_types.ts | 54 +- scripts/check_canonical_pins.ts | 31 +- scripts/doctor.ts | 66 +- scripts/patch_npm_package.ts | 43 +- scripts/publish.ts | 73 +- scripts/run_if_stale.ts | 6 +- scripts/size.ts | 2 +- scripts/test_npm.ts | 84 +- scripts/validate_artifacts.ts | 47 +- .../return_type_ternary_colon/expected.json | 1503 ++++++++++++++++ .../return_type_ternary_colon/input.svelte | 19 + .../expected.json | 783 +++++++++ .../input.svelte | 14 + .../unformatted_compact.svelte | 9 + .../comment_delimiter_body/expected.json | 1275 ++++++++++++++ .../regex/comment_delimiter_body/input.svelte | 33 + .../regex/keyword_operand_slash/expected.json | 1495 ++++++++++++++++ .../regex/keyword_operand_slash/input.svelte | 41 + .../regex/update_operand_slash/expected.json | 1144 +++++++++++++ .../regex/update_operand_slash/input.svelte | 27 + .../paren_binary_base_comment/expected.json | 1353 +++++++++++++++ .../paren_binary_base_comment/input.svelte | 21 + .../unformatted_compact.svelte | 16 + .../README.md | 50 + .../expected.json | 1516 +++++++++++++++++ .../input.svelte | 36 + .../output_prettier.svelte | 39 + .../non_null/long/expected.json | 799 ++++----- .../non_null/long/input.svelte | 4 - .../non_null/long/unformatted_compact.svelte | 3 - 112 files changed, 12734 insertions(+), 2739 deletions(-) create mode 100644 tests/fixtures/typescript/expressions/arrow/return_type_ternary_colon/expected.json create mode 100644 tests/fixtures/typescript/expressions/arrow/return_type_ternary_colon/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/chained/merged_head_callback_body_long/expected.json create mode 100644 tests/fixtures/typescript/expressions/calls/chained/merged_head_callback_body_long/input.svelte create mode 100644 tests/fixtures/typescript/expressions/calls/chained/merged_head_callback_body_long/unformatted_compact.svelte create mode 100644 tests/fixtures/typescript/expressions/literals/regex/comment_delimiter_body/expected.json create mode 100644 tests/fixtures/typescript/expressions/literals/regex/comment_delimiter_body/input.svelte create mode 100644 tests/fixtures/typescript/expressions/literals/regex/keyword_operand_slash/expected.json create mode 100644 tests/fixtures/typescript/expressions/literals/regex/keyword_operand_slash/input.svelte create mode 100644 tests/fixtures/typescript/expressions/literals/regex/update_operand_slash/expected.json create mode 100644 tests/fixtures/typescript/expressions/literals/regex/update_operand_slash/input.svelte create mode 100644 tests/fixtures/typescript/expressions/member/paren_binary_base_comment/expected.json create mode 100644 tests/fixtures/typescript/expressions/member/paren_binary_base_comment/input.svelte create mode 100644 tests/fixtures/typescript/expressions/member/paren_binary_base_comment/unformatted_compact.svelte create mode 100644 tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/README.md create mode 100644 tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/expected.json create mode 100644 tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/input.svelte create mode 100644 tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/output_prettier.svelte diff --git a/CLAUDE.md b/CLAUDE.md index 02a0c0ae4..86a092774 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -162,11 +162,21 @@ deno task doctor # one-pass setup check: runtimes, canonical pins + chec deno task typecheck # cargo check deno task test # cargo test deno task lint # cargo clippy -cargo fmt # format Rust code — the only autoformatter in this repo -# Non-Rust files (TS/MD/JSON) are hand-maintained: tsv ships NO prettier or deno -# fmt config. Never run `deno fmt` or `prettier` on the repo — with no config they -# reformat to their own defaults (spaces, double quotes) and churn every file. The -# fixture/corpus prettier oracles pass options inline, so they're unaffected. +cargo fmt # format Rust code +tsv format . # format the repo's own TS/JS — tsv formats itself (`--check` in the gate) +# tsv and cargo fmt are the repo's ONLY autoformatters, and they partition it: +# `cargo fmt` owns Rust, `tsv format` owns TS/JS. Markdown and JSON stay +# hand-maintained — nothing formats them. +# +# Never run `deno fmt` or `prettier` on the repo: tsv ships NO prettier or deno +# fmt config, so with no config they reformat to their own defaults (spaces, +# double quotes) and churn every file. The fixture/corpus prettier oracles pass +# options inline, so they're unaffected. +# +# `tsv format` on a DIRECTORY is safe — the root `.formatignore` prunes +# tests/fixtures/ and tests/fixtures_compile/, whose files are deliberately not +# format fixed points. Never name a file under tests/ explicitly: an explicit +# file argument bypasses the ignore files and would destroy the fixture's claim. cargo test --workspace test_typescript_parser_literal # run specific test by name cargo test --workspace --test fixtures_tests # fixture validation tests @@ -196,6 +206,7 @@ deno task compile:validation # validation-suite RATCHET over Svelte's ow deno task compile:validation:update # regenerate that snapshot after fixing a finding (or newly refusing a shape); refuses a narrowed run, and never pins a MISMATCH deno task compile:fuzz # differential compile fuzzer over feature cross-products — a discovery tool with an open work list, currently RED by design (sidecar-dependent, on demand; ./docs/compile_tooling.md) deno task pins:audit # canonical-oracle version sync (gated in `deno task check`): (1) pin agreement — sidecar.ts VERSIONS + npm: imports, benches/js/package.json, actor.rs acorn import-map must be identical; (2) checkout alignment — a PRESENT ../svelte or ../acorn-typescript checkout must match its pin (absent → skipped, so clean machines pass) +deno task format:audit # tsv formats its own TS/JS — `tsv format --check .` over the repo (gated in `deno task check`). Fails on EITHER a would-change file (exit 1) or a parse error (exit 2); no other gate covers this, since the corpus gates read OTHER repos deno task scan:audit # no new raw find/rfind/match_indices substring scans over source (gated in `deno task check`; ./docs/audits.md) deno task fanout:audit # no super-linear doc-node fanout — the per-layout-candidate rebuild blowup (gated in `deno task check`; ./docs/audits.md) deno task roundtrip:audit # format(tests/fixtures) must reparse — pure-Rust tripwire, real yield on external corpora (gated in `deno task check`; ./docs/audits.md) @@ -884,7 +895,8 @@ deno task metrics # shorthand **Audits** — the standing correctness gates and discovery harnesses: line-comment swallow, the print-once comment ledger, gap/blank injection, build-fanout, raw-find scan, -authoring-independence, format→reparse round-trip, comment↔token binding, +self-format (tsv over its own TS/JS), authoring-independence, format→reparse round-trip, +comment↔token binding, render-equivalence, layout-neutrality, the seeded mutational fuzzer, the F1 idempotency sweep, the `audit:corpus` bundle, the `lex_diff` differential lexer harness, and the two compiler audits (compile-fixture divergence integrity, the canonicalizer). Each is diff --git a/benches/js/bench.ts b/benches/js/bench.ts index cf9d61d18..4ea674a8a 100644 --- a/benches/js/bench.ts +++ b/benches/js/bench.ts @@ -67,7 +67,7 @@ import type { BenchmarkResult } from '@fuzdev/fuz_util/benchmark_types.ts'; import { benchmark_baseline_compare, benchmark_baseline_format, - benchmark_baseline_save, + benchmark_baseline_save } from '@fuzdev/fuz_util/benchmark_baseline.ts'; import { spawn_out } from '@fuzdev/fuz_util/process.ts'; import { mkdir, readFile, writeFile } from 'node:fs/promises'; @@ -80,7 +80,7 @@ import { canonical_parser_label, get_alternative_versions, get_benchmark_tasks, - init_implementations, + init_implementations } from './lib/implementations.ts'; import { type EffectiveCorpusEntry, @@ -98,19 +98,19 @@ import { generate_skipped_files_report, generate_summary_report, generate_versions_info, - type GroupResults, + type GroupResults } from './lib/report.ts'; import { type BinarySize, collect_binary_sizes, generate_binary_size_markdown, - generate_binary_size_report, + generate_binary_size_report } from './lib/binary_sizes.ts'; import { type Language, LANGUAGES, type SourceFile } from './lib/types.ts'; import { check_artifact_freshness, WASM_CRATES, - wasm_artifact_path, + wasm_artifact_path } from './lib/check_artifact_freshness.ts'; import { check_node_modules } from './lib/check_node_modules.ts'; import { get_library_path } from './lib/ffi.ts'; @@ -134,7 +134,7 @@ const Args_schema = z.strictObject({ 'save-baseline': z.boolean().default(false), 'compare-baseline': z.boolean().default(false), 'save-report': z.boolean().default(false), - verbose: z.boolean().default(false), + verbose: z.boolean().default(false) }); // Strip leading -- from deno task passthrough. `argv.slice(2)` (node:process) is @@ -150,7 +150,7 @@ if (!parsed.success) { .map((k) => `--${k}`); console.error( 'Invalid arguments:', - parsed.error.issues.map((i: { message: string }) => i.message).join(', '), + parsed.error.issues.map((i: { message: string }) => i.message).join(', ') ); console.error(`Known flags: ${known.join(', ')}`); exit(1); @@ -167,7 +167,7 @@ const args = { save_baseline: parsed.data['save-baseline'], compare_baseline: parsed.data['compare-baseline'], save_report: parsed.data['save-report'], - verbose: parsed.data.verbose, + verbose: parsed.data.verbose }; // Baseline statistics requested — raises the sample floors in the timed suite @@ -209,7 +209,7 @@ const NOISE_PATTERNS = [ // oxfmt 0.50 wraps the call site in backticks (`oxfmt::textToDoc()`), // so match the unwrapped function name to survive minor wording shifts. 'oxfmt::textToDoc', - 'panicked at crates/biome_rowan', + 'panicked at crates/biome_rowan' ]; const original_console_error = console.error.bind(console); const suppressed_noise = new Map(); @@ -316,7 +316,7 @@ const IS_CONFORMANCE = CORPUS_MODE === 'conformance'; if (IS_CONFORMANCE && (args.save_baseline || args.compare_baseline)) { console.error( 'Baseline flags are perf-corpus only — drop --save-baseline/--compare-baseline ' + - 'or run without BENCH_CORPUS=conformance.', + 'or run without BENCH_CORPUS=conformance.' ); exit(1); } @@ -361,7 +361,7 @@ if (COVERAGE_ONLY && !IS_CONFORMANCE) { // null-timing entries — corrupting the throughput headline. Reject the combo. console.error( 'BENCH_COVERAGE_ONLY=1 requires BENCH_CORPUS=conformance (it is a conformance-only mode; ' + - 'running it in perf mode would overwrite the perf report with null-timing entries).', + 'running it in perf mode would overwrite the perf report with null-timing entries).' ); exit(1); } @@ -389,7 +389,7 @@ const RESULTS_DIR = './benches/js/results'; log('Loading corpus...\n'); const corpus_loader = new DevReposLoader(CORPUS_MODE, { - allow_missing: env.BENCH_ALLOW_MISSING === '1', + allow_missing: env.BENCH_ALLOW_MISSING === '1' }); // Drain `stream()` directly instead of `load()` so we skip the loader's // own corpus summary — bench.ts prints its own tighter one below that @@ -407,7 +407,7 @@ const by_language = group_by_language(files); const total_file_counts = { svelte: by_language.svelte.length, typescript: by_language.typescript.length, - css: by_language.css.length, + css: by_language.css.length }; // Apply file filter and limit @@ -427,7 +427,7 @@ const is_limited = MAX_FILES_PER_LANGUAGE !== undefined || FILE_FILTER !== undef const bytes_by_language: Record = { svelte: svelte_files.reduce((sum, f) => sum + f.bytes, 0), typescript: ts_files.reduce((sum, f) => sum + f.bytes, 0), - css: css_files.reduce((sum, f) => sum + f.bytes, 0), + css: css_files.reduce((sum, f) => sum + f.bytes, 0) }; /** @@ -443,26 +443,24 @@ function format_throughput(bytes_per_sec: number): string { // limited, each line reads `N of M files` so the subset is obvious. const total_files = svelte_files.length + ts_files.length + css_files.length; const total_bytes = bytes_by_language.svelte + bytes_by_language.typescript + bytes_by_language.css; -const fmt_count = ( - n: number, - total: number, -) => (is_limited && n !== total ? `${n} of ${total}` : `${n}`); +const fmt_count = (n: number, total: number) => + is_limited && n !== total ? `${n} of ${total}` : `${n}`; const fmt_bytes = (b: number) => `${(b / 1_000_000).toFixed(1)} MB`; log(`Corpus (${CORPUS_MODE} view):`); log( - ` Svelte: ${fmt_count(svelte_files.length, total_file_counts.svelte).padEnd(11)} files (${ - fmt_bytes(bytes_by_language.svelte) - })`, + ` Svelte: ${fmt_count(svelte_files.length, total_file_counts.svelte).padEnd(11)} files (${fmt_bytes( + bytes_by_language.svelte + )})` ); log( - ` TypeScript: ${fmt_count(ts_files.length, total_file_counts.typescript).padEnd(11)} files (${ - fmt_bytes(bytes_by_language.typescript) - })`, + ` TypeScript: ${fmt_count(ts_files.length, total_file_counts.typescript).padEnd(11)} files (${fmt_bytes( + bytes_by_language.typescript + )})` ); log( - ` CSS: ${fmt_count(css_files.length, total_file_counts.css).padEnd(11)} files (${ - fmt_bytes(bytes_by_language.css) - })`, + ` CSS: ${fmt_count(css_files.length, total_file_counts.css).padEnd(11)} files (${fmt_bytes( + bytes_by_language.css + )})` ); log(` Total: ${String(total_files).padEnd(11)} files (${fmt_bytes(total_bytes)})`); log(); @@ -472,19 +470,20 @@ log(); // WASM artifacts are runtime-specific: Deno executes the FFI library + the // `deno`-target WASM bundle; Node/Bun execute the N-API addon + the `nodejs` // target. `wasm_artifact_path` resolves the runtime's bundle itself. -const native_check = RUNTIME === 'deno' - ? { - label: `FFI (${env.TSV_FFI_PROFILE ?? 'release'})`, - path: get_library_path(), - binding_crates: ['tsv_ffi'], - rebuild: 'deno task build:ffi', - } - : { - label: 'N-API', - path: get_napi_library_path(), - binding_crates: ['tsv_napi'], - rebuild: 'deno task build:napi', - }; +const native_check = + RUNTIME === 'deno' + ? { + label: `FFI (${env.TSV_FFI_PROFILE ?? 'release'})`, + path: get_library_path(), + binding_crates: ['tsv_ffi'], + rebuild: 'deno task build:ffi' + } + : { + label: 'N-API', + path: get_napi_library_path(), + binding_crates: ['tsv_napi'], + rebuild: 'deno task build:napi' + }; const wasm_target = RUNTIME === 'deno' ? 'deno' : 'nodejs'; await check_artifact_freshness([ native_check, @@ -492,8 +491,8 @@ await check_artifact_freshness([ label: `WASM (all/${wasm_target})`, path: wasm_artifact_path('all'), binding_crates: WASM_CRATES, - rebuild: `deno task build:wasm:all:${wasm_target}`, - }, + rebuild: `deno task build:wasm:all:${wasm_target}` + } ]); // Friendly preflight: the canonical impls (prettier + svelte/compiler) resolve @@ -586,7 +585,7 @@ function enforce_perf_coverage(): void { console.error( `Perf corpus: ${violations.length} unlisted pre-flight failure(s). Every in-scope tool must ` + `process every real-world file — fix the tool, or add a reviewed entry (with a reason) to ` + - `PERF_OMITS in lib/perf_omit.ts:\n${violations.join('\n')}`, + `PERF_OMITS in lib/perf_omit.ts:\n${violations.join('\n')}` ); exit(1); } @@ -644,12 +643,12 @@ function warn_variant_parity(): void { native: name, wasm: sibling_name, native_only, - wasm_only, + wasm_only }); console.error( `⚠ variant parity (${group_name}): ${name} and ${sibling_name} accept different files ` + `(${native_only} ${name}-only, ${wasm_only} ${sibling_name}-only). Same engine — a ` + - `divergence usually means one binding's error surface is broken, not an engine difference.`, + `divergence usually means one binding's error surface is broken, not an engine difference.` ); } } @@ -671,7 +670,7 @@ function process_corpus(files: SourceFile[], process_fn: (file: SourceFile) => v /** Async variant of `process_corpus`. */ async function process_corpus_async( files: SourceFile[], - process_fn: (file: SourceFile) => Promise, + process_fn: (file: SourceFile) => Promise ): Promise { for (const file of files) { await process_fn(file); @@ -682,7 +681,7 @@ async function process_corpus_async( const files_by_language: Record = { svelte: svelte_files, typescript: ts_files, - css: css_files, + css: css_files }; /** @@ -697,7 +696,7 @@ const files_by_language: Record = { async function run_preflight( tasks: ReturnType, files: SourceFile[], - language: Language, + language: Language ): Promise { for (let i = 0; i < tasks.length; i++) { const task = tasks[i]; @@ -756,7 +755,7 @@ const group_setups: Map = new Map(); */ async function run_preflight_group( operation: 'parse' | 'format', - language: Language, + language: Language ): Promise { const files = files_by_language[language]; if (files.length === 0) return; @@ -765,7 +764,7 @@ async function run_preflight_group( log(`\n· ${group_name}`); const tasks = get_benchmark_tasks(impls, operation, language, { - forced_async: BENCH_FORCED_ASYNC, + forced_async: BENCH_FORCED_ASYNC }); await run_preflight(tasks, files, language); @@ -804,7 +803,7 @@ async function run_preflight_group( const success_set = successful_files.get(task.tracking_key) ?? new Set(); filtered_files_by_task.set( task.tracking_key, - files.filter((f) => success_set.has(f.path)), + files.filter((f) => success_set.has(f.path)) ); } } @@ -815,7 +814,10 @@ async function run_preflight_group( // `Nx (Mf)` annotation in the bench-table `vs baseline` column. for (const task of tasks) { const task_files = filtered_files_by_task.get(task.tracking_key)!; - effective_corpus_bytes.set(task.tracking_key, task_files.reduce((sum, f) => sum + f.bytes, 0)); + effective_corpus_bytes.set( + task.tracking_key, + task_files.reduce((sum, f) => sum + f.bytes, 0) + ); iterated_file_count.set(task.tracking_key, task_files.length); } @@ -825,7 +827,7 @@ async function run_preflight_group( /** Run the timed measurement loop for one group using its stashed pre-flight setup. */ async function run_benchmark_group( operation: 'parse' | 'format', - language: Language, + language: Language ): Promise { const group_name = `${operation}/${language}`; const setup = group_setups.get(group_name); @@ -864,15 +866,16 @@ async function run_benchmark_group( // the displayed MB/s is what this impl actually achieved, not // what it would have done on the full corpus. const tracking_key = task_tracking.get(result.name); - const effective_bytes = tracking_key ? effective_corpus_bytes.get(tracking_key) ?? 0 : 0; + const effective_bytes = tracking_key ? (effective_corpus_bytes.get(tracking_key) ?? 0) : 0; // Mirror the report-path guard (`generate_group_throughput_markdown`): // with an empty intersection the MB/s figure is a misleading `0.0 MB/s` // while ops/sec is real, so print `—` instead of a fake throughput. - const throughput = effective_bytes === 0 - ? '—' - : format_throughput(result.stats.ops_per_second * effective_bytes); + const throughput = + effective_bytes === 0 + ? '—' + : format_throughput(result.stats.ops_per_second * effective_bytes); log(` [${index + 1}/${total}] ${result.name}: ${ops_per_sec} sweeps/sec (${throughput})`); - }, + } }); for (const task of tasks) { @@ -901,7 +904,7 @@ async function run_benchmark_group( await task.run_async!(f.content, language, f.goal); }); }, - async: true, + async: true }); } else { bench.add({ @@ -909,7 +912,7 @@ async function run_benchmark_group( fn: () => { process_corpus(task_files, (f) => task.run(f.content, language, f.goal)); }, - async: false, + async: false }); } } @@ -941,7 +944,9 @@ if (CORPUS_MODE === 'perf') { } if (COVERAGE_ONLY) { - log('\nCoverage-only mode: skipping the timed benchmark phase (coverage is a pre-flight product).'); + log( + '\nCoverage-only mode: skipping the timed benchmark phase (coverage is a pre-flight product).' + ); } else { log('\nRunning benchmarks:'); for (const lang of LANGUAGES) { @@ -1117,7 +1122,7 @@ const NULL_STATS = { std_dev_ns: null, cv: null, ops_per_second: null, - sample_size: null, + sample_size: null } as const; /** @@ -1143,7 +1148,7 @@ function build_coverage_entries(): BaselineEntry[] { files_processed: coverage?.processed ?? null, files_total: coverage?.total ?? null, files_iterated: iterated ?? null, - runtime: RUNTIME, + runtime: RUNTIME }); } } @@ -1156,7 +1161,7 @@ async function build_results_data( groups: GroupResults[], corpus: { svelte: number; typescript: number; css: number }, versions: BaselineVersions, - binary_sizes: BinarySize[], + binary_sizes: BinarySize[] ): Promise { const entries: BaselineEntry[] = []; if (COVERAGE_ONLY) { @@ -1188,7 +1193,7 @@ async function build_results_data( files_processed: coverage?.processed ?? null, files_total: coverage?.total ?? null, files_iterated: iterated ?? null, - runtime: RUNTIME, + runtime: RUNTIME }); } } @@ -1210,7 +1215,7 @@ async function build_results_data( binary_sizes: binary_sizes, entries, suppressed_noise: Object.fromEntries(suppressed_noise), - variant_parity: variant_parity_findings, + variant_parity: variant_parity_findings }; } @@ -1233,19 +1238,17 @@ function generate_markdown_report( effective_size: Map, effective_bytes: Map, iterated_counts: Map, - skipped: Map>, + skipped: Map> ): string { const lines: string[] = []; lines.push( - IS_CONFORMANCE - ? '# tsv conformance benchmark results (parse)\n' - : '# tsv benchmark results\n', + IS_CONFORMANCE ? '# tsv conformance benchmark results (parse)\n' : '# tsv benchmark results\n' ); const commit_str = git_commit ? ` (${git_commit})` : ''; lines.push(`**Runtime:** ${RUNTIME}\n`); lines.push( `**Machine:** ${machine.cpu_model} · ${machine.os}/${machine.arch} · ` + - `${RUNTIME} ${machine.runtime_version}\n`, + `${RUNTIME} ${machine.runtime_version}\n` ); const conformance_note = COVERAGE_ONLY ? 'conformance — fixtures-only corpus (disjoint from perf; Svelte set minus svelte/compiler-rejected files), parse groups only; per-tool Coverage lines only (coverage-only run — timed throughput skipped)' @@ -1253,7 +1256,7 @@ function generate_markdown_report( lines.push( `**Corpus kind:** ${ IS_CONFORMANCE ? conformance_note : 'perf — real-world code only (fixture suites excluded)' - }\n`, + }\n` ); lines.push(`**Date:** ${timestamp} — tsv ${versions.tsv}${commit_str}\n`); @@ -1263,11 +1266,11 @@ function generate_markdown_report( `**Corpus:** ${corpus.svelte} Svelte (${format_mb(corpus_bytes.svelte)}), ` + `${corpus.typescript} TypeScript (${format_mb(corpus_bytes.typescript)}), ` + `${corpus.css} CSS (${format_mb(corpus_bytes.css)}) — ` + - `${total_files} files, ${format_mb(total_bytes)} total\n`, + `${total_files} files, ${format_mb(total_bytes)} total\n` ); if (corpus_loader.sources.length > 0) { lines.push( - `**Sources:** ${corpus_loader.sources.map((s) => `${s.path} (${s.files})`).join(', ')}\n`, + `**Sources:** ${corpus_loader.sources.map((s) => `${s.path} (${s.files})`).join(', ')}\n` ); } @@ -1277,7 +1280,7 @@ function generate_markdown_report( `acorn@${versions.acorn}`, `acorn-typescript@${versions.acorn_ts}`, `prettier@${versions.prettier}`, - `prettier-plugin-svelte@${versions.prettier_svelte}`, + `prettier-plugin-svelte@${versions.prettier_svelte}` ]; if (versions.oxc_parser) version_parts.push(`oxc-parser@${versions.oxc_parser}`); if (versions.oxfmt) version_parts.push(`oxfmt@${versions.oxfmt}`); @@ -1291,14 +1294,14 @@ function generate_markdown_report( 'over the group\u2019s iterated file set, so the absolute columns (sweeps/sec, p50\u2013p99, min/max) ' + 'are per-sweep, not per-file — divide by the group\u2019s file count (the Files lines / `(Mf)` ' + 'annotations) for per-file figures; ratios and MB/s are denominated consistently either way. ' + - 'This is single-core throughput, not the multi-core batch throughput a CLI gets formatting many files at once.\n', + 'This is single-core throughput, not the multi-core batch throughput a CLI gets formatting many files at once.\n' ); // Coverage-only run: no timed groups exist, so render the per-tool coverage // tables straight from pre-flight state (the timed loop below no-ops). if (COVERAGE_ONLY) { lines.push( - ...generate_coverage_only_markdown(LANGUAGES, OPERATIONS, task_tracking, effective_size), + ...generate_coverage_only_markdown(LANGUAGES, OPERATIONS, task_tracking, effective_size) ); } @@ -1324,7 +1327,7 @@ function generate_markdown_report( lines.push(`## ${group.name}\n`); lines.push( - generate_group_bench_table_markdown(group.results, baseline_exists ? baseline : undefined), + generate_group_bench_table_markdown(group.results, baseline_exists ? baseline : undefined) ); lines.push(''); @@ -1350,7 +1353,7 @@ function generate_markdown_report( // mean self is faster than the opponent. File counts are surfaced per // group (Files / Coverage lines) and per row in the Comparisons tables. lines.push( - '_Note: every `Nx` is speedup form — values > 1 mean self is faster. File counts come from the per-group `Files (intersection):` / `Coverage:` lines and the Comparisons table row labels._\n', + '_Note: every `Nx` is speedup form — values > 1 mean self is faster. File counts come from the per-group `Files (intersection):` / `Coverage:` lines and the Comparisons table row labels._\n' ); } @@ -1365,7 +1368,7 @@ function generate_markdown_report( groups, LANGUAGES, iterated_counts, - task_tracking, + task_tracking ); if (comparison_markdown) { lines.push(comparison_markdown); @@ -1381,7 +1384,7 @@ function generate_markdown_report( skipped, MAX_ERROR_MESSAGE_LENGTH, args.verbose, - task_tracking, + task_tracking ); if (skipped_markdown) { lines.push(skipped_markdown); @@ -1406,7 +1409,7 @@ async function save_results( data: Baseline, groups: GroupResults[], binary_sizes: BinarySize[], - write_report: boolean, + write_report: boolean ): Promise { await mkdir(RESULTS_DIR, { recursive: true }); const timestamp = data.timestamp.replace(/[:.]/g, '-').slice(0, 19); @@ -1426,18 +1429,18 @@ async function save_results( effective_corpus_size, effective_corpus_bytes, iterated_file_count, - skipped_files, + skipped_files ); const json = JSON.stringify(data, null, '\t'); const writes: Promise[] = [ writeFile(`${base_path}.json`, json), - writeFile(`${base_path}.md`, markdown), + writeFile(`${base_path}.md`, markdown) ]; if (write_report) { writes.push( writeFile(`${RESULTS_DIR}/report.${REPORT_TAG}.json`, json), - writeFile(`${RESULTS_DIR}/report.${REPORT_TAG}.md`, markdown), + writeFile(`${RESULTS_DIR}/report.${REPORT_TAG}.md`, markdown) ); } await Promise.all(writes); @@ -1473,7 +1476,7 @@ function build_baseline_metadata(data: Baseline): Record { return { corpus: data.corpus, versions: data.versions, - binary_sizes: data.binary_sizes, + binary_sizes: data.binary_sizes }; } @@ -1486,7 +1489,7 @@ interface BaselineMeta { async function save_baseline(data: Baseline): Promise { await benchmark_baseline_save(flatten_results_for_baseline(all_group_results), { path: BASELINE_DIR, - metadata: build_baseline_metadata(data), + metadata: build_baseline_metadata(data) }); log(`Baseline saved to ${BASELINE_DIR}/baseline.json`); } @@ -1511,13 +1514,13 @@ async function compare_baseline(current: Baseline): Promise { regression_threshold: 1.0, // Mark the baseline stale after a week so a long-untouched baseline // doesn't quietly mask drift accumulated over months. - staleness_warning_days: 7, - }, + staleness_warning_days: 7 + } ); if (!comparison.baseline_found) { console.error( - `\nNo baseline found at ${BASELINE_DIR}/baseline.json. Run with --save-baseline first.`, + `\nNo baseline found at ${BASELINE_DIR}/baseline.json. Run with --save-baseline first.` ); return; } @@ -1532,17 +1535,18 @@ async function compare_baseline(current: Baseline): Promise { // would silently move with the corpus otherwise). const meta = comparison.baseline_metadata as BaselineMeta | null; const baseline_corpus = meta?.corpus; - const corpus_match = baseline_corpus && + const corpus_match = + baseline_corpus && baseline_corpus.svelte === current.corpus.svelte && baseline_corpus.typescript === current.corpus.typescript && baseline_corpus.css === current.corpus.css; if (baseline_corpus && !corpus_match) { log(`\n⚠️ Corpus size differs from baseline:`); log( - ` Baseline: svelte=${baseline_corpus.svelte}, ts=${baseline_corpus.typescript}, css=${baseline_corpus.css}`, + ` Baseline: svelte=${baseline_corpus.svelte}, ts=${baseline_corpus.typescript}, css=${baseline_corpus.css}` ); log( - ` Current: svelte=${current.corpus.svelte}, ts=${current.corpus.typescript}, css=${current.corpus.css}`, + ` Current: svelte=${current.corpus.svelte}, ts=${current.corpus.typescript}, css=${current.corpus.css}` ); } @@ -1562,14 +1566,14 @@ const binary_sizes = await collect_binary_sizes({ has_wasm: !!impls.wasm, has_oxc: !!impls.oxc, has_biome: !!impls.biome, - has_dprint: !!impls.dprint, + has_dprint: !!impls.dprint }); // Build results data (used by all output paths and always saved) const corpus = { svelte: svelte_files.length, typescript: ts_files.length, - css: css_files.length, + css: css_files.length }; const alt_versions = get_alternative_versions(impls); const v = impls.versions.canonical; @@ -1580,7 +1584,7 @@ const versions: BaselineVersions = { acorn_ts: v['@sveltejs/acorn-typescript'], prettier: v.prettier, prettier_svelte: v['prettier-plugin-svelte'], - ...alt_versions, + ...alt_versions }; const results_data = await build_results_data(all_group_results, corpus, versions, binary_sizes); @@ -1602,8 +1606,8 @@ if (args.json) { effective_corpus_size, effective_corpus_bytes, iterated_file_count, - skipped_files, - ), + skipped_files + ) ); } else { // Standard text output. The timed summary is empty in coverage-only mode — @@ -1616,7 +1620,7 @@ if (args.json) { const effective_corpus_report = generate_effective_corpus_report( effective_corpus_size, - task_tracking_by_group, + task_tracking_by_group ); if (effective_corpus_report) { console.log(effective_corpus_report); @@ -1626,7 +1630,7 @@ if (args.json) { skipped_files, MAX_ERROR_MESSAGE_LENGTH, args.verbose, - task_tracking_by_group, + task_tracking_by_group ); if (skipped_report) { console.log(skipped_report); @@ -1645,8 +1649,8 @@ if (args.json) { all_group_results, LANGUAGES, iterated_file_count, - task_tracking_by_group, - ), + task_tracking_by_group + ) ); } @@ -1670,7 +1674,7 @@ const results_path = await save_results( results_data, all_group_results, binary_sizes, - write_report, + write_report ); log(`\nResults saved to:`); log(` ${results_path}.json`); diff --git a/benches/js/compose_reports.ts b/benches/js/compose_reports.ts index 59147eead..01e2ea587 100644 --- a/benches/js/compose_reports.ts +++ b/benches/js/compose_reports.ts @@ -75,7 +75,7 @@ if (reports.size === 0) { console.error( 'No per-runtime reports found in results/ ' + '(report.deno.json / report.node.json / report.bun.json).\n' + - 'Run `deno task bench` (or a per-runtime bench) first.', + 'Run `deno task bench` (or a per-runtime bench) first.' ); exit(1); } @@ -120,9 +120,10 @@ const sources = present.map((r) => ({ timestamp: reports.get(r)!.timestamp, git_commit: reports.get(r)!.git_commit, tsv: reports.get(r)!.versions?.tsv ?? null, - machine: reports.get(r)!.machine ?? null, + machine: reports.get(r)!.machine ?? null })); -const mixed_vintage = new Set(sources.map((s) => `${s.git_commit ?? '?'}@${s.tsv ?? '?'}`)).size > 1; +const mixed_vintage = + new Set(sources.map((s) => `${s.git_commit ?? '?'}@${s.tsv ?? '?'}`)).size > 1; // Loud flag when the siblings were produced on DIFFERENT boxes — the // throughput numbers are machine-relative, so cross-runtime ratios are only @@ -154,9 +155,9 @@ const combined = { name: row.name, ops_per_second: row.ops, mean_ns: row.mean_ns, - files_iterated: row.files_iterated, + files_iterated: row.files_iterated }; - }), + }) }; // Markdown: one table per group, runtimes side by side + ratio vs base_runtime. @@ -176,13 +177,13 @@ md.push('# tsv benchmark results — cross-runtime\n'); md.push(`**Generated:** ${combined.generated}\n`); md.push( `**Runtimes:** ${present.join(', ')} ` + - '— each runtime’s full report is its `report..{json,md}` sibling.\n', + '— each runtime’s full report is its `report..{json,md}` sibling.\n' ); for (const s of sources) { const rt_ver = s.machine ? ` ${s.machine.runtime_version}` : ''; md.push( `- \`${s.runtime}\`${rt_ver}: ${s.git_commit ?? 'unknown commit'} @ ${s.timestamp}` + - `${s.tsv ? ` (tsv ${s.tsv})` : ''}`, + `${s.tsv ? ` (tsv ${s.tsv})` : ''}` ); } md.push(''); @@ -193,14 +194,14 @@ if (mixed_machine) { md.push( '⚠ **Mixed machines** — the sibling reports were produced on different ' + 'hardware, so the cross-runtime ratios are not comparable; re-run every ' + - 'runtime on one machine (`deno task bench:perf`).\n', + 'runtime on one machine (`deno task bench:perf`).\n' ); } if (mixed_vintage) { md.push( '⚠ **Mixed vintages** — the sibling reports above come from different ' + 'commits/versions, so the cross-runtime ratios are unreliable; re-run the ' + - 'stale runtimes (`deno task bench:perf` refreshes all three).\n', + 'stale runtimes (`deno task bench:perf` refreshes all three).\n' ); } md.push( @@ -209,7 +210,7 @@ md.push( `\`${base_runtime}\` (> 1 = faster than ${base_runtime}). A group (or row) ` + 'flagged `⚠ files …` iterated *different per-runtime intersections* (each ' + 'runtime times the files all its impls passed preflight on), so a sliver ' + - 'of the ratio can be file-set difference rather than runtime effect.\n', + 'of the ratio can be file-set difference rather than runtime effect.\n' ); const groups: string[] = []; @@ -247,18 +248,19 @@ for (const group of groups) { const header = [ 'Impl', ...present.map((r) => `${r} sweeps/sec`), - ...others.map((r) => `${r}/${base_runtime}`), + ...others.map((r) => `${r}/${base_runtime}`) ]; md.push(`| ${header.join(' | ')} |`); md.push(`| ${header.map((_, i) => (i === 0 ? '---' : '---:')).join(' | ')} |`); for (const row of group_rows) { - const name_cell = !uniform && files_unequal(row.files_iterated) - ? `${row.name} ⚠ files ${fmt_file_counts(row.files_iterated)}` - : row.name; + const name_cell = + !uniform && files_unequal(row.files_iterated) + ? `${row.name} ⚠ files ${fmt_file_counts(row.files_iterated)}` + : row.name; const cells = [ name_cell, ...present.map((r) => fmt_ops(row.ops[r])), - ...others.map((r) => fmt_ratio(row.ops[r], row.ops[base_runtime])), + ...others.map((r) => fmt_ratio(row.ops[r], row.ops[base_runtime])) ]; md.push(`| ${cells.join(' | ')} |`); } @@ -272,14 +274,14 @@ if (mixed_vintage) { console.error( '⚠ compose: sibling reports have MIXED VINTAGES (' + sources.map((s) => `${s.runtime}=${(s.git_commit ?? '?').slice(0, 8)}`).join(' ') + - ') — cross-runtime ratios unreliable; re-run the stale runtimes.', + ') — cross-runtime ratios unreliable; re-run the stale runtimes.' ); } if (mixed_machine) { console.error( '⚠ compose: sibling reports were produced on DIFFERENT machines (' + sources.map((s) => `${s.runtime}=${s.machine?.cpu_model ?? '?'}`).join(' | ') + - ') — cross-runtime ratios are not comparable; re-run every runtime on one box.', + ') — cross-runtime ratios are not comparable; re-run every runtime on one box.' ); } console.log(`Composed cross-runtime report from: ${present.join(', ')}`); diff --git a/benches/js/conformance.ts b/benches/js/conformance.ts index f0968511f..f0e593dac 100644 --- a/benches/js/conformance.ts +++ b/benches/js/conformance.ts @@ -37,7 +37,7 @@ if (Deno.args.length > 0) { console.error( 'The conformance driver takes no arguments — use the per-leg tasks for scoped runs\n' + '(conformance:svelte-fixtures / conformance:ts-fixtures / conformance:ts-repo /\n' + - ' corpus:compare:parse / corpus:compare:format).', + ' corpus:compare:parse / corpus:compare:format).' ); Deno.exit(1); } @@ -88,9 +88,9 @@ async function run_render_audit(): Promise { '--', 'render_audit', '--gate', - ...dirs, + ...dirs ], - { stdio: 'inherit' }, + { stdio: 'inherit' } ); if (status !== 0) Deno.exit(status ?? 1); } @@ -101,7 +101,7 @@ const legs: [string, () => Promise][] = [ ['conformance:ts-repo', () => run_ts_repo_compare([])], ['corpus:compare:parse --all', () => run_corpus_compare_parse(['--all'])], ['corpus:compare:format --all', () => run_corpus_compare_format(['--all'])], - ['render:audit (pinned checkouts)', run_render_audit], + ['render:audit (pinned checkouts)', run_render_audit] ]; const run_started = performance.now(); @@ -119,7 +119,7 @@ for (const [name, leg] of legs) { console.error(`──── ${name} OK (${((performance.now() - leg_started) / 1000).toFixed(1)}s)`); } console.error( - `\n✓ conformance aggregate: all ${legs.length} legs green in ${ - ((performance.now() - run_started) / 60_000).toFixed(1) - }m`, + `\n✓ conformance aggregate: all ${legs.length} legs green in ${( + (performance.now() - run_started) / 60_000 + ).toFixed(1)}m` ); diff --git a/benches/js/corpus_audit.ts b/benches/js/corpus_audit.ts index 335334d0e..17e88d78b 100644 --- a/benches/js/corpus_audit.ts +++ b/benches/js/corpus_audit.ts @@ -49,7 +49,7 @@ const real_dirs = await corpus_present_dirs('perf', log); const prettier_suites = [ '../prettier/tests/format/typescript', '../prettier/tests/format/js', - '../prettier/tests/format/css', + '../prettier/tests/format/css' ].filter((p) => { if (existsSync(p)) return true; log(` ⚠ prettier suite missing, skipped: ${p}`); @@ -69,13 +69,23 @@ log('building --profile corpus tsv_debug (--features audits) …'); const build = spawnSync( 'cargo', ['build', '--profile', 'corpus', '-q', '-p', 'tsv_debug', '--features', 'audits'], - { stdio: 'inherit' }, + { stdio: 'inherit' } ); if (build.status !== 0) { log('Error: build failed.'); process.exit(build.status ?? 1); } -const cargo_run = ['run', '--profile', 'corpus', '-q', '-p', 'tsv_debug', '--features', 'audits', '--']; +const cargo_run = [ + 'run', + '--profile', + 'corpus', + '-q', + '-p', + 'tsv_debug', + '--features', + 'audits', + '--' +]; interface Leg { name: string; @@ -87,18 +97,33 @@ interface Leg { } const legs: Leg[] = [ - { name: 'roundtrip_audit --gate', args: ['roundtrip_audit', '--gate'], dirs: all_dirs, gating: true }, + { + name: 'roundtrip_audit --gate', + args: ['roundtrip_audit', '--gate'], + dirs: all_dirs, + gating: true + }, { name: 'comment_audit', args: ['comment_audit'], dirs: all_dirs, gating: true }, - { name: 'binding_audit --gate (real code)', args: ['binding_audit', '--gate'], dirs: real_dirs, gating: true }, + { + name: 'binding_audit --gate (real code)', + args: ['binding_audit', '--gate'], + dirs: real_dirs, + gating: true + }, { name: 'binding_audit --gate (prettier suites)', args: ['binding_audit', '--gate'], dirs: prettier_suites, gating: false, - note: 'report-only: a few known adversarial philosophy HARDs (plain comments tsv preserves in place)', + note: 'report-only: a few known adversarial philosophy HARDs (plain comments tsv preserves in place)' }, { name: 'authoring_audit', args: ['authoring_audit'], dirs: real_dirs, gating: true }, - { name: 'fuzz --iterations 0 (F1 sweep)', args: ['fuzz', '--iterations', '0'], dirs: real_dirs, gating: true }, + { + name: 'fuzz --iterations 0 (F1 sweep)', + args: ['fuzz', '--iterations', '0'], + dirs: real_dirs, + gating: true + } ]; let failed = false; @@ -109,7 +134,7 @@ for (const leg of legs) { } log(`\n─ ${leg.name}${leg.gating ? '' : ' [report-only]'}${leg.note ? ` — ${leg.note}` : ''}`); const { status } = spawnSync('cargo', [...cargo_run, ...leg.args, ...leg.dirs], { - stdio: 'inherit', + stdio: 'inherit' }); if (status !== 0) { if (leg.gating) { diff --git a/benches/js/corpus_compare_format.ts b/benches/js/corpus_compare_format.ts index f86b574da..6d0afcf2b 100644 --- a/benches/js/corpus_compare_format.ts +++ b/benches/js/corpus_compare_format.ts @@ -24,19 +24,19 @@ import { redirect_logs_to_stderr, rel_path, resolve_compare_base_path, - run_compare_main, + run_compare_main } from './lib/compare_cli.ts'; import { diff_lines, type DiffHunk, extract_hunks, filter_diff_context, - format_diff_for_terminal, + format_diff_for_terminal } from './lib/diff.ts'; import { CORPUS_FORMAT_MATCH_MIN, CORPUS_FORMAT_PARTIAL_PIN, - CORPUS_FORMAT_UNKNOWN_PIN, + CORPUS_FORMAT_UNKNOWN_PIN } from './lib/gate_counts.ts'; import { type Language, LANGUAGES } from './lib/types.ts'; import { @@ -44,7 +44,7 @@ import { check_safety_vs_prettier, detect_divergences, type HunkCoverageResult, - type SafetyViolation, + type SafetyViolation } from './lib/divergence/mod.ts'; const CorpusCompareArgs = z.object({ @@ -54,7 +54,7 @@ const CorpusCompareArgs = z.object({ explain: z.boolean().default(false), strict: z.boolean().default(false), 'audit-patterns': z.boolean().default(false), - summary: z.boolean().default(false), + summary: z.boolean().default(false) }); interface LanguageStats { @@ -153,14 +153,14 @@ function empty_stats(): LanguageStats { unknown_diff: 0, safety_violation: 0, expected_errors: 0, - errors: 0, + errors: 0 }; } /** All stored results across languages with the given status (sorted callers chain `.sort`). */ function results_by_status( results: Map, - status: CompareResult['status'], + status: CompareResult['status'] ): CompareResult[] { return LANGUAGES.flatMap((lang) => results.get(lang)!.filter((r) => r.status === status)); } @@ -227,7 +227,7 @@ function stats_to_counts(s: LanguageStats) { unknown: s.unknown_diff, safety: s.safety_violation, errors: s.errors, - expected_errors: s.expected_errors, + expected_errors: s.expected_errors }; } @@ -255,7 +255,7 @@ function build_stats_block(stats: Map) { function json_file_entry( r: CompareResult, lang: Language, - base_path: string, + base_path: string ): Record { const base = { path: rel_path(r.path, base_path), language: lang, bytes: r.bytes }; switch (r.status) { @@ -283,13 +283,14 @@ function json_file_entry( function build_json_report( results: Map, stats: Map, - base_path: string, + base_path: string ): Record { const by_status = (status: CompareResult['status']) => LANGUAGES.flatMap((lang) => - results.get(lang)!.filter((r) => r.status === status).map((r) => - json_file_entry(r, lang, base_path) - ) + results + .get(lang)! + .filter((r) => r.status === status) + .map((r) => json_file_entry(r, lang, base_path)) ); return { stats: build_stats_block(stats), @@ -297,7 +298,7 @@ function build_json_report( partial: by_status('partial_divergence'), unknown: by_status('unknown_diff'), errors: by_status('error'), - expected_errors: by_status('expected_error'), + expected_errors: by_status('expected_error') }; } @@ -399,16 +400,17 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro pattern_name: string, file_path: string, hunk_indices: number[], - hunks: DiffHunk[], + hunks: DiffHunk[] ): void { const entries = pattern_audit_map.get(pattern_name) ?? []; const first_hunk = hunks[hunk_indices[0]]; const preview = (first_hunk?.added_lines[0] || first_hunk?.removed_lines[0] || '') - .trim().slice(0, 60); + .trim() + .slice(0, 60); entries.push({ path: rel_path(file_path, base_path), hunk_indices, - hunk_preview: preview, + hunk_preview: preview }); pattern_audit_map.set(pattern_name, entries); } @@ -417,7 +419,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro function tally_patterns( coverage: HunkCoverageResult, file_path: string, - hunks: DiffHunk[], + hunks: DiffHunk[] ): void { for (const d of coverage.matches) { divergence_counts.set(d.pattern, (divergence_counts.get(d.pattern) || 0) + 1); @@ -469,7 +471,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro // surfaced rather than silently suppressing a safety verdict. if (prettier.trim() === '' && file.content.trim() !== '') { throw new Error( - 'prettier returned empty output for non-empty source (prettier miss — safety verdict unreliable)', + 'prettier returned empty output for non-empty source (prettier miss — safety verdict unreliable)' ); } @@ -510,7 +512,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro path: file.path, bytes: file.bytes, status: 'known_divergence', - coverage, + coverage }); tally_patterns(coverage, file.path, hunks); } else { @@ -526,7 +528,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro const ours_verify = native.format(file.content, lang); if (ours_verify !== ours) { throw new Error( - 'native format nondeterminism: two runs on identical input differ (FFI corruption — safety verdict unreliable)', + 'native format nondeterminism: two runs on identical input differ (FFI corruption — safety verdict unreliable)' ); } lang_stats.safety_violation++; @@ -535,7 +537,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro bytes: file.bytes, status: 'safety_violation', safety_violations, - coverage: coverage.classification !== 'none_explained' ? coverage : undefined, + coverage: coverage.classification !== 'none_explained' ? coverage : undefined }); if (exit_on_first) { const rel = rel_path(file.path, base_path); @@ -566,7 +568,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro bytes: file.bytes, status: 'unknown_diff', ours, - prettier, + prettier }); if (exit_on_first) { console.log(`\nDifference (strict mode): ${rel}`); @@ -583,7 +585,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro path: file.path, bytes: file.bytes, status: 'known_divergence', - coverage, + coverage }); tally_patterns(coverage, file.path, hunks); } else if (coverage.classification === 'partial') { @@ -594,7 +596,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro path: file.path, bytes: file.bytes, status: 'partial_divergence', - coverage, + coverage }); tally_patterns(coverage, file.path, hunks); } else { @@ -607,7 +609,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro status: 'unknown_diff', ours, prettier, - coverage, + coverage }); if (exit_on_first) { console.log(`\nUnknown difference: ${rel}`); @@ -615,7 +617,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro const removals = diff.filter((d) => d.type === 'remove').length; const additions = diff.filter((d) => d.type === 'add').length; console.log( - `Diff: \x1b[31m- Prettier\x1b[0m → \x1b[32m+ Ours\x1b[0m (${removals} prettier-only, ${additions} ours-only)`, + `Diff: \x1b[31m- Prettier\x1b[0m → \x1b[32m+ Ours\x1b[0m (${removals} prettier-only, ${additions} ours-only)` ); console.log(''); for (const line of format_diff_for_terminal(filter_diff_context(diff))) { @@ -636,7 +638,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro bytes: file.bytes, status: 'expected_error', error: error_msg, - expected_reason: expected_check.pattern!.reason, + expected_reason: expected_check.pattern!.reason }); } else { lang_stats.errors++; @@ -644,7 +646,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro path: file.path, bytes: file.bytes, status: 'error', - error: error_msg, + error: error_msg }); if (exit_on_first) { console.log(`\nError: ${rel_path(file.path, base_path)}`); @@ -732,7 +734,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro unknown_diff: total_unknown_diff, safety_violation: total_safety_violation, expected_errors: total_expected_errors, - errors: total_errors, + errors: total_errors }; const parts = build_detail_parts(totals); @@ -767,9 +769,10 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro console.log(`\n${pattern}: ${entries.length} files`); const samples = entries.slice(0, 3); for (const sample of samples) { - const hunk_str = sample.hunk_indices.length === 1 - ? `hunk ${sample.hunk_indices[0]}` - : `hunks ${sample.hunk_indices.join(',')}`; + const hunk_str = + sample.hunk_indices.length === 1 + ? `hunk ${sample.hunk_indices[0]}` + : `hunks ${sample.hunk_indices.join(',')}`; console.log(` ${sample.path} (${hunk_str})`); if (sample.hunk_preview) { console.log(` "${sample.hunk_preview}"`); @@ -795,28 +798,27 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro } // Show partial divergences (some hunks unexplained) - const all_partial = results_by_status(results, 'partial_divergence') - .sort((a, b) => a.bytes - b.bytes); + const all_partial = results_by_status(results, 'partial_divergence').sort( + (a, b) => a.bytes - b.bytes + ); // Show unknown differences (needs investigation) - const all_unknown = results_by_status(results, 'unknown_diff') - .sort((a, b) => a.bytes - b.bytes); + const all_unknown = results_by_status(results, 'unknown_diff').sort((a, b) => a.bytes - b.bytes); // Default: show unexplained diffs (partial hunks + unknown files) // --summary: compact output without diffs if (summary) { // Compact partial divergence listing if (all_partial.length > 0) { - console.log( - `\nPartial Divergences (${all_partial.length} files):`, - ); + console.log(`\nPartial Divergences (${all_partial.length} files):`); for (const r of all_partial.slice(0, 10)) { const coverage = r.coverage!; const patterns = coverage.matches.map((d) => d.pattern).join(', '); console.log( - ` ${ - rel_path(r.path, base_path) - }: ${patterns} (${coverage.unexplained_hunks.length} unexplained hunks)`, + ` ${rel_path( + r.path, + base_path + )}: ${patterns} (${coverage.unexplained_hunks.length} unexplained hunks)` ); } if (all_partial.length > 10) { @@ -826,9 +828,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro // Compact unknown differences listing if (all_unknown.length > 0) { - console.log( - `\nUnknown Differences (${all_unknown.length} files, needs investigation):`, - ); + console.log(`\nUnknown Differences (${all_unknown.length} files, needs investigation):`); for (const r of all_unknown.slice(0, 10)) { const size_str = format_bytes(r.bytes); const diff_summary = get_diff_summary(r.prettier!, r.ours!); @@ -844,7 +844,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro const total_unexplained_files = all_partial.length + all_unknown.length; if (total_unexplained_files > 0) { console.log( - `\nUnexplained Differences (${all_partial.length} partial + ${all_unknown.length} unknown = ${total_unexplained_files} files):`, + `\nUnexplained Differences (${all_partial.length} partial + ${all_unknown.length} unknown = ${total_unexplained_files} files):` ); } @@ -858,16 +858,12 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro const explained_count = coverage.explained_hunks.size; const total_hunks = coverage.hunks.length; console.log(`\n ${rel_path(r.path, base_path)}:`); - console.log( - ` explained ${explained_count}/${total_hunks} hunks: ${patterns}`, - ); + console.log(` explained ${explained_count}/${total_hunks} hunks: ${patterns}`); for (const idx of coverage.unexplained_hunks) { const hunk = coverage.hunks[idx]; const ours_label = hunk.ours_range ? `ours:${hunk.ours_range.start}` : ''; const prettier_label = hunk.prettier_range ? `prettier:${hunk.prettier_range.start}` : ''; - console.log( - ` \x1b[33mhunk ${idx}\x1b[0m: @@ ${ours_label} / ${prettier_label} @@`, - ); + console.log(` \x1b[33mhunk ${idx}\x1b[0m: @@ ${ours_label} / ${prettier_label} @@`); for (const line of format_diff_for_terminal(hunk.lines)) { console.log(` ${line}`); } @@ -885,7 +881,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro const additions = diff.filter((d) => d.type === 'add').length; console.log(`\n ${rel_path(r.path, base_path)} (${format_bytes(r.bytes)}):`); console.log( - ` \x1b[31m-${removals} prettier-only\x1b[0m, \x1b[32m+${additions} ours-only\x1b[0m`, + ` \x1b[31m-${removals} prettier-only\x1b[0m, \x1b[32m+${additions} ours-only\x1b[0m` ); for (const line of format_diff_for_terminal(filter_diff_context(diff))) { console.log(` ${line}`); @@ -922,8 +918,9 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro } // Show expected errors (dimmed, verbose/explain only for details) - const all_expected_errors = results_by_status(results, 'expected_error') - .sort((a, b) => a.bytes - b.bytes); + const all_expected_errors = results_by_status(results, 'expected_error').sort( + (a, b) => a.bytes - b.bytes + ); if (all_expected_errors.length > 0 && (verbose || explain)) { console.log(`\n\x1b[2mExpected Errors (${all_expected_errors.length} files):\x1b[0m`); @@ -960,28 +957,31 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro console.log( `\n\x1b[33mWARN: live dev-repo divergences (non-gating) — ${live_warn.join('; ')}. ` + `Unversioned working trees, so not pinned; triage a specific one with ` + - `\`corpus:compare:format \`. SAFETY still gates these.\x1b[0m`, + `\`corpus:compare:format \`. SAFETY still gates these.\x1b[0m` ); } const pin = (lang: Language) => repro_stats.get(lang)!; const pin_failures = [ ...LANGUAGES.filter((lang) => pin(lang).match < CORPUS_FORMAT_MATCH_MIN[lang]).map( - (lang) => `${lang} match ${pin(lang).match} < pinned minimum ${CORPUS_FORMAT_MATCH_MIN[lang]}`, + (lang) => + `${lang} match ${pin(lang).match} < pinned minimum ${CORPUS_FORMAT_MATCH_MIN[lang]}` ), ...LANGUAGES.filter((lang) => pin(lang).unknown_diff !== CORPUS_FORMAT_UNKNOWN_PIN[lang]).map( - (lang) => `${lang} unknown ${pin(lang).unknown_diff} ≠ pinned ${CORPUS_FORMAT_UNKNOWN_PIN[lang]}`, + (lang) => + `${lang} unknown ${pin(lang).unknown_diff} ≠ pinned ${CORPUS_FORMAT_UNKNOWN_PIN[lang]}` ), ...LANGUAGES.filter( - (lang) => pin(lang).partial_divergence !== CORPUS_FORMAT_PARTIAL_PIN[lang], + (lang) => pin(lang).partial_divergence !== CORPUS_FORMAT_PARTIAL_PIN[lang] ).map( - (lang) => `${lang} partial ${pin(lang).partial_divergence} ≠ pinned ${CORPUS_FORMAT_PARTIAL_PIN[lang]}`, - ), + (lang) => + `${lang} partial ${pin(lang).partial_divergence} ≠ pinned ${CORPUS_FORMAT_PARTIAL_PIN[lang]}` + ) ]; if (pin_failures.length > 0) { console.log( `\n\x1b[31mFAIL: pinned counts (reproducible subset) — ${pin_failures.join('; ')}. ` + - `If deliberate, re-pin in lib/gate_counts.ts.\x1b[0m`, + `If deliberate, re-pin in lib/gate_counts.ts.\x1b[0m` ); canonical.dispose(); native.dispose(); @@ -993,7 +993,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro console.log(); if (total_safety_violation > 0) { console.log( - `\x1b[31mFAIL: ${total_safety_violation} safety violations (data loss detected)\x1b[0m`, + `\x1b[31mFAIL: ${total_safety_violation} safety violations (data loss detected)\x1b[0m` ); canonical.dispose(); native.dispose(); @@ -1008,9 +1008,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro const parts: string[] = []; if (total_unknown_diff > 0) parts.push(`${total_unknown_diff} unknown`); if (total_partial_divergence > 0) parts.push(`${total_partial_divergence} partial`); - console.log( - `\x1b[33mWARN: ${parts.join(', ')} differences (may need investigation)\x1b[0m`, - ); + console.log(`\x1b[33mWARN: ${parts.join(', ')} differences (may need investigation)\x1b[0m`); } else if ( total_errors > 0 && total_match + total_known_divergence + total_expected_errors === 0 @@ -1019,7 +1017,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro // NOT a graded run. Without this floor the SAFETY differential never runs // on any file and the gate would WARN + exit 0 having compared nothing. console.log( - `\x1b[31mFAIL: all ${total_errors} files errored — nothing was compared (systemic sidecar/FFI failure?)\x1b[0m`, + `\x1b[31mFAIL: all ${total_errors} files errored — nothing was compared (systemic sidecar/FFI failure?)\x1b[0m` ); canonical.dispose(); native.dispose(); @@ -1043,7 +1041,7 @@ export async function run_corpus_compare_format(argv: string[] = Deno.args): Pro */ function build_error_json_report(message: string): Record { const empty_lang_stats: Map = new Map( - LANGUAGES.map((lang) => [lang, empty_stats()]), + LANGUAGES.map((lang) => [lang, empty_stats()]) ); return { stats: build_stats_block(empty_lang_stats), @@ -1052,7 +1050,7 @@ function build_error_json_report(message: string): Record { unknown: [], errors: [], expected_errors: [], - error: message, + error: message }; } diff --git a/benches/js/corpus_compare_parse.ts b/benches/js/corpus_compare_parse.ts index d2b82035c..84b688872 100644 --- a/benches/js/corpus_compare_parse.ts +++ b/benches/js/corpus_compare_parse.ts @@ -45,14 +45,17 @@ import { redirect_logs_to_stderr, rel_path, resolve_compare_base_path, - run_compare_main, + run_compare_main } from './lib/compare_cli.ts'; import { CORPUS_PARSE_COMPARED_MIN, CORPUS_PARSE_TSV_ERRORS_PIN } from './lib/gate_counts.ts'; import { type Language, LANGUAGES } from './lib/types.ts'; const CorpusCompareParseArgs = z.object({ ...COMPARE_BASE_ARG_FIELDS, - 'multibyte-only': z.boolean().default(false).meta({ aliases: ['m'] }), + 'multibyte-only': z + .boolean() + .default(false) + .meta({ aliases: ['m'] }) }); /** Per-file diff cap — collection stops here and the file is flagged truncated. */ @@ -62,11 +65,7 @@ const MAX_DIFFS_PER_FILE = 50; const MAX_GROUP_SAMPLES = 3; type DiffKind = - | 'value_mismatch' - | 'type_mismatch' - | 'missing_ours' - | 'missing_canonical' - | 'length_mismatch'; + 'value_mismatch' | 'type_mismatch' | 'missing_ours' | 'missing_canonical' | 'length_mismatch'; export interface DiffEntry { /** Concrete path into the AST, e.g. `body[3].declarations[0].init.start` */ @@ -113,7 +112,7 @@ function empty_stats(): LanguageStats { undocumented: 0, tsv_errors: 0, canonical_errors: 0, - both_errors: 0, + both_errors: 0 }; } @@ -211,7 +210,7 @@ interface DocumentedMatcher { matches: ( entry: Omit, canonical_parent: unknown, - ctx: MatchContext, + ctx: MatchContext ) => boolean; } @@ -243,7 +242,7 @@ const DOCUMENTED_MATCHERS: DocumentedMatcher[] = [ return Array.isArray(starts) && new Set(starts).size !== starts.length; } return false; - }, + } }, { // Svelte parses `\n', - 'svelte', - ); - assert(out.includes(''), `expected disambiguating comma in .svelte, got: ${out}`); - } finally { - impl.dispose(); +Deno.test( + 'canonical: single-type-param arrow keeps `` in .svelte (JSX disambiguation)', + async () => { + const impl = await make_canonical(); + try { + // In a Svelte `\n', + 'svelte' + ); + assert(out.includes(''), `expected disambiguating comma in .svelte, got: ${out}`); + } finally { + impl.dispose(); + } } -}); +); diff --git a/benches/js/lib/check_artifact_freshness.ts b/benches/js/lib/check_artifact_freshness.ts index c5c284dfa..5ad37c42f 100644 --- a/benches/js/lib/check_artifact_freshness.ts +++ b/benches/js/lib/check_artifact_freshness.ts @@ -55,14 +55,7 @@ import { current_runtime } from './runtime.ts'; * (`tsv_debug`, `tsv_cli`): they don't feed the measured artifacts, and * including them would force wasm rebuilds on every fixture-workflow edit. */ -export const CORE_CRATES = [ - 'tsv_lang', - 'tsv_arena', - 'tsv_html', - 'tsv_ts', - 'tsv_css', - 'tsv_svelte', -]; +export const CORE_CRATES = ['tsv_lang', 'tsv_arena', 'tsv_html', 'tsv_ts', 'tsv_css', 'tsv_svelte']; /** * Crates that feed the WASM bundle beyond `CORE_CRATES`: the binding crate @@ -188,7 +181,7 @@ export async function check_artifact_freshness(checks: readonly ArtifactCheck[]) label: check.label, path: check.path, reason: 'missing', - rebuild: check.rebuild, + rebuild: check.rebuild }); continue; } @@ -202,7 +195,7 @@ export async function check_artifact_freshness(checks: readonly ArtifactCheck[]) rebuild: check.rebuild, source_path: source.path, artifact_ms, - source_ms: source.ms, + source_ms: source.ms }); } } @@ -217,7 +210,7 @@ export async function check_artifact_freshness(checks: readonly ArtifactCheck[]) lines.push( fatal ? '✗ Stale benchmark artifacts — refusing to measure outdated binaries.' - : '⚠ Stale benchmark artifacts (BENCH_STALE_OK=1 — measuring anyway).', + : '⚠ Stale benchmark artifacts (BENCH_STALE_OK=1 — measuring anyway).' ); for (const s of stale) { if (s.reason === 'missing') { @@ -225,18 +218,16 @@ export async function check_artifact_freshness(checks: readonly ArtifactCheck[]) } else { lines.push( ` • ${s.label}: built ${fmt_mtime(s.artifact_ms!)}, ` + - `but ${s.source_path} changed ${fmt_mtime(s.source_ms!)}`, + `but ${s.source_path} changed ${fmt_mtime(s.source_ms!)}` ); } lines.push(` rebuild: ${s.rebuild}`); } if (fatal) { lines.push(''); + lines.push(' Rebuild everything first with a build-first task (`deno task bench` /'); lines.push( - ' Rebuild everything first with a build-first task (`deno task bench` /', - ); - lines.push( - ' `deno task corpus:compare:format`), or `deno task build:bench` then re-run `deno task smoke`,', + ' `deno task corpus:compare:format`), or `deno task build:bench` then re-run `deno task smoke`,' ); lines.push(' run the specific rebuild command(s) above, or set BENCH_STALE_OK=1 to override'); lines.push(' (the override applies to stale artifacts only — a missing one is always fatal).'); @@ -252,6 +243,6 @@ export async function check_artifact_freshness(checks: readonly ArtifactCheck[]) export function wasm_artifact_path(variant: 'format' | 'parse' | 'all'): string { const target = current_runtime() === 'deno' ? 'deno' : 'nodejs'; return fileURLToPath( - new URL(`../../../crates/tsv_wasm/pkg/${variant}/${target}/tsv_wasm_bg.wasm`, import.meta.url), + new URL(`../../../crates/tsv_wasm/pkg/${variant}/${target}/tsv_wasm_bg.wasm`, import.meta.url) ); } diff --git a/benches/js/lib/check_node_modules.ts b/benches/js/lib/check_node_modules.ts index f9f73f312..9560f68e5 100644 --- a/benches/js/lib/check_node_modules.ts +++ b/benches/js/lib/check_node_modules.ts @@ -21,9 +21,7 @@ import { fileURLToPath } from 'node:url'; import process from 'node:process'; export type NodeModulesStatus = - | { status: 'ok' } - | { status: 'missing'; message: string } - | { status: 'stale'; message: string }; + { status: 'ok' } | { status: 'missing'; message: string } | { status: 'stale'; message: string }; /** Inspect `benches/js/node_modules` without side effects (doctor-friendly). */ export async function probe_node_modules(): Promise { @@ -35,7 +33,7 @@ export async function probe_node_modules(): Promise { status: 'missing', message: `Harness dependencies not installed (${node_modules} missing). ` + - "Run 'deno task bench:install' first — it installs the npm deps + the oxc wasi binding.", + "Run 'deno task bench:install' first — it installs the npm deps + the oxc wasi binding." }; } @@ -43,7 +41,7 @@ export async function probe_node_modules(): Promise { let stamp_mtime: number; try { const stamp = await stat( - fileURLToPath(new URL('../node_modules/.package-lock.json', import.meta.url)), + fileURLToPath(new URL('../node_modules/.package-lock.json', import.meta.url)) ); stamp_mtime = stamp.mtimeMs; } catch { @@ -55,7 +53,7 @@ export async function probe_node_modules(): Promise { status: 'stale', message: 'benches/js/package.json is newer than the installed node_modules — ' + - "re-run 'deno task bench:install' so the run matches the pins", + "re-run 'deno task bench:install' so the run matches the pins" }; } diff --git a/benches/js/lib/compare_cli.ts b/benches/js/lib/compare_cli.ts index 63587dd95..a3ac5bb18 100644 --- a/benches/js/lib/compare_cli.ts +++ b/benches/js/lib/compare_cli.ts @@ -28,12 +28,27 @@ import { load_all_versions } from './versions.ts'; */ export const COMPARE_BASE_ARG_FIELDS = { _: z.array(z.string()).default(() => []), - all: z.boolean().default(false).meta({ aliases: ['a'] }), - filter: z.string().optional().meta({ aliases: ['f'] }), - limit: z.number().optional().meta({ aliases: ['l'] }), - verbose: z.boolean().default(false).meta({ aliases: ['v'] }), + all: z + .boolean() + .default(false) + .meta({ aliases: ['a'] }), + filter: z + .string() + .optional() + .meta({ aliases: ['f'] }), + limit: z + .number() + .optional() + .meta({ aliases: ['l'] }), + verbose: z + .boolean() + .default(false) + .meta({ aliases: ['v'] }), json: z.boolean().default(false), - help: z.boolean().default(false).meta({ aliases: ['h'] }), + help: z + .boolean() + .default(false) + .meta({ aliases: ['h'] }) } as const; /** Get relative path from base directory. */ @@ -78,7 +93,7 @@ export function emit_json_stdout(report: Record): void { */ export function create_compare_loader( use_all: boolean, - base_path: string, + base_path: string ): DevReposLoader | DirectoryLoader { return use_all ? new DevReposLoader('gates') : new DirectoryLoader(base_path); } @@ -106,8 +121,8 @@ export async function init_compare_implementations(): Promise<{ label: `FFI (${ffi_profile})`, path: get_library_path(), binding_crates: ['tsv_ffi'], - rebuild, - }, + rebuild + } ]); const versions = await load_all_versions(); @@ -141,7 +156,7 @@ export async function init_compare_implementations(): Promise<{ export function run_compare_main( main: () => Promise, schema: z.ZodType, - build_error_report: (message: string) => Record, + build_error_report: (message: string) => Record ): void { main().catch((e) => { const message = e instanceof Error ? e.message : String(e); diff --git a/benches/js/lib/corpus.ts b/benches/js/lib/corpus.ts index 3c711c53f..35b6aef75 100644 --- a/benches/js/lib/corpus.ts +++ b/benches/js/lib/corpus.ts @@ -73,7 +73,7 @@ const DEFAULT_EXCLUSIONS = [ // is excluded separately in `should_exclude` (segment-prefix match). '/_errors_/', '/front-matter/', - '/cursor/', + '/cursor/' ]; /** @@ -174,7 +174,7 @@ interface WalkOptions { * pruning exactly as before. */ async function* walk_corpus( dir_path: string, - options: WalkOptions = {}, + options: WalkOptions = {} ): AsyncGenerator { const extensions = options.extensions ?? DEFAULT_EXTENSIONS; const ext_set = new Set(extensions.map((e) => `.${e.toLowerCase()}`)); @@ -198,7 +198,7 @@ async function* walk_corpus( path, content, language, - bytes: new TextEncoder().encode(content).length, + bytes: new TextEncoder().encode(content).length }; } catch (e) { console.warn(`Warning: Could not read ${path}: ${e}`); @@ -223,9 +223,9 @@ async function* load_file_list(list_path: string): AsyncGenerator { } // Accept both the `{path, goal}` shape (test262, goal-aware) and a bare // `string[]` (older caches / other file lists — goal defaults to module). - const entries = raw.map((e) => (typeof e === 'string' ? {path: e} : e)); + const entries = raw.map((e) => (typeof e === 'string' ? { path: e } : e)); entries.sort((a, b) => a.path.localeCompare(b.path)); - for (const {path: relative, goal} of entries) { + for (const { path: relative, goal } of entries) { const path = resolve(relative); const language = detect_language(path); if (!language) continue; @@ -236,7 +236,7 @@ async function* load_file_list(list_path: string): AsyncGenerator { content, language, bytes: new TextEncoder().encode(content).length, - goal, + goal }; } catch (e) { console.warn(`Warning: Could not read ${path}: ${e}`); @@ -261,7 +261,7 @@ export function group_by_language(files: SourceFile[]): Record f.language === 'svelte'), typescript: files.filter((f) => f.language === 'typescript'), - css: files.filter((f) => f.language === 'css'), + css: files.filter((f) => f.language === 'css') }; } @@ -387,7 +387,7 @@ const should_prune_perf = (relative: string): boolean => { export async function* stream_perf_candidate(dir: string): AsyncGenerator { yield* walk_corpus(dir, { prune_build_output: false, - skip: (_path, relative) => should_prune_perf(relative), + skip: (_path, relative) => should_prune_perf(relative) }); } @@ -428,7 +428,7 @@ const CORPUS_ENTRIES: CorpusEntry[] = [ tier: 'real', extensions: ['css'], optional: true, - hint: 'run `deno task bench:harvest:svelte-styles`', + hint: 'run `deno task bench:harvest:svelte-styles`' }, // External projects (monorepo subpaths). Each entry is a reviewed package // `src/` tree — never a whole monorepo — so test fixtures, build output, and @@ -446,7 +446,7 @@ const CORPUS_ENTRIES: CorpusEntry[] = [ path: '../prettier-plugin-svelte/test', tier: 'prettier_fixture', extensions: ['html'], - skip: has_companion_options, + skip: has_companion_options }, // Prettier test cases (formatting edge cases and regression tests) { path: '../prettier/tests/format/typescript', tier: 'prettier_fixture' }, @@ -463,14 +463,14 @@ const CORPUS_ENTRIES: CorpusEntry[] = [ tier: 'suite', extensions: ['css'], optional: true, - hint: 'run `deno task bench:harvest:wpt` (needs ../wpt)', + hint: 'run `deno task bench:harvest:wpt` (needs ../wpt)' }, { files_from: 'benches/js/.cache/test262_files.json', tier: 'suite', optional: true, - hint: 'run `deno task bench:harvest:test262` (needs ../test262)', - }, + hint: 'run `deno task bench:harvest:test262` (needs ../test262)' + } ]; const TIERS_BY_VIEW: Record = { @@ -486,7 +486,7 @@ const TIERS_BY_VIEW: Record = { // are mutually exclusive sets. perf is the "every in-scope tool must fully // process it" corpus (bench.ts hard-fails an unlisted failure); conformance is // the hard-cases-only surface where sub-100% coverage is the measurement. - conformance: ['prettier_fixture', 'suite'], + conformance: ['prettier_fixture', 'suite'] }; /** @@ -564,7 +564,7 @@ export interface CorpusSource { * `scripts/doctor.ts` — kept here so the doctor and the loader can't drift. */ export async function corpus_missing_entries( - view: CorpusView, + view: CorpusView ): Promise<{ missing: string[]; optional_missing: string[]; total: number }> { const tiers = TIERS_BY_VIEW[view]; const entries = CORPUS_ENTRIES.filter((e) => tiers.includes(e.tier)); @@ -574,7 +574,7 @@ export async function corpus_missing_entries( const entry_path = entry_source(entry); if (await fs_exists(resolve(entry_path))) continue; (entry.optional ? optional_missing : missing).push( - entry_path + (entry.hint ? ` (${entry.hint})` : ''), + entry_path + (entry.hint ? ` (${entry.hint})` : '') ); } return { missing, optional_missing, total: entries.length }; @@ -594,7 +594,7 @@ export async function corpus_missing_entries( */ export function corpus_present_dirs( view: CorpusView, - logger: Logger = console.log, + logger: Logger = console.log ): Promise { return corpus_present_dirs_for_tiers(TIERS_BY_VIEW[view], logger); } @@ -611,7 +611,7 @@ export function corpus_present_dirs( */ export async function corpus_present_dirs_for_tiers( tiers: readonly CorpusTier[], - logger: Logger = console.log, + logger: Logger = console.log ): Promise { const dirs: string[] = []; for (const entry of CORPUS_ENTRIES.filter((e) => tiers.includes(e.tier))) { @@ -657,7 +657,10 @@ export class DevReposLoader { */ sources: CorpusSource[] = []; - constructor(view: CorpusView, options?: { allow_missing?: boolean; apply_reject_cache?: boolean }) { + constructor( + view: CorpusView, + options?: { allow_missing?: boolean; apply_reject_cache?: boolean } + ) { this.view = view; this.allow_missing = options?.allow_missing ?? false; this.apply_reject_cache = options?.apply_reject_cache ?? true; @@ -676,7 +679,9 @@ export class DevReposLoader { if (await fs_exists(resolve(entry_path))) { present.push(entry); } else if (entry.optional) { - logger(` ⚠ optional corpus entry missing: ${entry_path}${entry.hint ? ` — ${entry.hint}` : ''}`); + logger( + ` ⚠ optional corpus entry missing: ${entry_path}${entry.hint ? ` — ${entry.hint}` : ''}` + ); } else { // Prefer an entry's own remedy (a harvest task for the derived // caches); otherwise a concrete `git clone` line for a known @@ -694,7 +699,7 @@ export class DevReposLoader { throw new Error( `Missing corpus entr${missing.length === 1 ? 'y' : 'ies'} (${this.view} view): ` + `${missing.join(', ')} — clone the missing repo(s), or opt into a partial ` + - `corpus with allow_missing (BENCH_ALLOW_MISSING=1 for the bench).`, + `corpus with allow_missing (BENCH_ALLOW_MISSING=1 for the bench).` ); } } @@ -713,7 +718,7 @@ export class DevReposLoader { reject_set ? ` canonical-reject cache: excluding ${reject_set.size} Svelte files rejected by svelte/compiler` : ` ⚠ canonical-reject cache absent — Svelte coverage counts svelte/compiler's rejects ` + - `(run \`deno task bench:harvest:svelte-rejects\`)`, + `(run \`deno task bench:harvest:svelte-rejects\`)` ); } let reject_excluded = 0; @@ -739,25 +744,24 @@ export class DevReposLoader { const base_skip = entry.skip; // `reject_set` holds only Svelte paths (harvested Svelte-only), so a // bare membership test is inherently language-scoped. - const skip: SkipFn | undefined = this.view === 'perf' - ? async (path, relative) => - should_prune_perf(relative) || ((await base_skip?.(path, relative)) ?? false) - : reject_set - ? async (path, relative) => { - if (reject_set.has(path)) { - reject_excluded++; - return true; - } - return (await base_skip?.(path, relative)) ?? false; - } - : base_skip; - for await ( - const file of walk_corpus(resolved_path, { - extensions: entry.extensions, - skip, - prune_build_output: false, - }) - ) { + const skip: SkipFn | undefined = + this.view === 'perf' + ? async (path, relative) => + should_prune_perf(relative) || ((await base_skip?.(path, relative)) ?? false) + : reject_set + ? async (path, relative) => { + if (reject_set.has(path)) { + reject_excluded++; + return true; + } + return (await base_skip?.(path, relative)) ?? false; + } + : base_skip; + for await (const file of walk_corpus(resolved_path, { + extensions: entry.extensions, + skip, + prune_build_output: false + })) { count++; by_language[file.language]++; file.reproducible = reproducible; @@ -780,7 +784,7 @@ export class DevReposLoader { // prompted for the `:run` (skip-harvest) path. logger( ` ⚠ canonical-reject cache drift: excluded ${reject_excluded} of ${reject_set.size} ` + - `cached paths — re-run \`deno task bench:harvest:svelte-rejects\``, + `cached paths — re-run \`deno task bench:harvest:svelte-rejects\`` ); } } diff --git a/benches/js/lib/corpus_repos.ts b/benches/js/lib/corpus_repos.ts index fc6dd6b19..b6a0cc80b 100644 --- a/benches/js/lib/corpus_repos.ts +++ b/benches/js/lib/corpus_repos.ts @@ -33,7 +33,7 @@ const exec_file = promisify(execFile); */ const CACHE_CANONICAL: Record = { 'benches/js/.cache/wpt_css': 'https://github.com/web-platform-tests/wpt', - 'benches/js/.cache/test262_files.json': 'https://github.com/tc39/test262', + 'benches/js/.cache/test262_files.json': 'https://github.com/tc39/test262' }; /** @@ -51,12 +51,12 @@ const CLONE_URL_BY_PREFIX: Array = [ ['../acorn-typescript', 'https://github.com/sveltejs/acorn-typescript'], ['../typescript', 'https://github.com/microsoft/TypeScript'], ['../wpt', 'https://github.com/web-platform-tests/wpt'], - ['../test262', 'https://github.com/tc39/test262'], + ['../test262', 'https://github.com/tc39/test262'] ]; async function git(cwd: string, args: string[]): Promise { try { - const {stdout} = await exec_file('git', args, {cwd}); + const { stdout } = await exec_file('git', args, { cwd }); const out = stdout.trim(); return out.length > 0 ? out : null; } catch { @@ -89,7 +89,7 @@ export async function detect_repo(source_path: string): Promise { source.repo = (await detect_repo(source.path)) ?? undefined; - }), + }) ); } diff --git a/benches/js/lib/diff.ts b/benches/js/lib/diff.ts index 719bb3ddb..dde830fd1 100644 --- a/benches/js/lib/diff.ts +++ b/benches/js/lib/diff.ts @@ -144,14 +144,16 @@ export function extract_hunks(diff: DiffLine[]): DiffHunk[] { hunks.push({ index: hunks.length, lines: current_lines, - ours_range: hunk_ours_start !== null && hunk_ours_end !== null - ? { start: hunk_ours_start, end: hunk_ours_end } - : null, - prettier_range: hunk_prettier_start !== null && hunk_prettier_end !== null - ? { start: hunk_prettier_start, end: hunk_prettier_end } - : null, + ours_range: + hunk_ours_start !== null && hunk_ours_end !== null + ? { start: hunk_ours_start, end: hunk_ours_end } + : null, + prettier_range: + hunk_prettier_start !== null && hunk_prettier_end !== null + ? { start: hunk_prettier_start, end: hunk_prettier_end } + : null, added_lines: added_lines, - removed_lines: removed_lines, + removed_lines: removed_lines }); current_lines = []; @@ -252,7 +254,7 @@ export function format_diff_for_terminal(diff: DiffLine[], use_color = true): st // Expand tabs for consistent display, then find max width among lines exceeding threshold const expanded_lines = diff.map((d) => ({ ...d, - expanded: expand_tabs(d.line), + expanded: expand_tabs(d.line) })); let max_width = 0; diff --git a/benches/js/lib/divergence/expected_errors.ts b/benches/js/lib/divergence/expected_errors.ts index fda36d848..2048a526f 100644 --- a/benches/js/lib/divergence/expected_errors.ts +++ b/benches/js/lib/divergence/expected_errors.ts @@ -70,13 +70,13 @@ export const EXPECTED_ERROR_PATTERNS: ExpectedErrorPattern[] = [ name: 'scss_style', reason: "SCSS in `; - const ours = - ``; + const ours = ``; const ctx = make_context(ours, prettier, 'svelte'); const match = run_pattern('css_atrule_long_wrap', ctx); assertNotEquals(match, null); @@ -1148,43 +1221,57 @@ Deno.test('css_selector_divergence: negative - || in TypeScript (logical OR)', ( assertEquals(match, null); }); -Deno.test('css_selector_divergence: positive - nested pseudo-args re-indent (:is in :where)', () => { - // prettier indents the :is() arg list one extra level (`nodes.length > 2`); tsv - // keys on a real combinator, so the same list sits one tab shallower. Pure re-indent. - const prettier = ':where(\n\t:is(\n\t\t\tp,\n\t\t\tul\n\t\t):not(:last-child)\n) {\n\tmargin: 0;\n}'; - const ours = ':where(\n\t:is(\n\t\tp,\n\t\tul\n\t):not(:last-child)\n) {\n\tmargin: 0;\n}'; - const ctx = make_context(ours, prettier, 'css'); - const match = run_pattern('css_selector_divergence', ctx); - assertNotEquals(match, null); -}); - -Deno.test('css_selector_divergence: negative - selector content actually differs (not pure re-indent)', () => { - // Same shape, but a selector token changed (`ul` → `ol`): NOT a pure re-indent, - // so the pseudo-args clause must not claim it (a real content difference). - const prettier = ':where(\n\t:is(\n\t\t\tp,\n\t\t\tul\n\t):not(:last-child)\n) {\n\tmargin: 0;\n}'; - const ours = ':where(\n\t:is(\n\t\tp,\n\t\tol\n\t):not(:last-child)\n) {\n\tmargin: 0;\n}'; - const ctx = make_context(ours, prettier, 'css'); - const match = run_pattern('css_selector_divergence', ctx); - assertEquals(match, null); -}); +Deno.test( + 'css_selector_divergence: positive - nested pseudo-args re-indent (:is in :where)', + () => { + // prettier indents the :is() arg list one extra level (`nodes.length > 2`); tsv + // keys on a real combinator, so the same list sits one tab shallower. Pure re-indent. + const prettier = + ':where(\n\t:is(\n\t\t\tp,\n\t\t\tul\n\t\t):not(:last-child)\n) {\n\tmargin: 0;\n}'; + const ours = ':where(\n\t:is(\n\t\tp,\n\t\tul\n\t):not(:last-child)\n) {\n\tmargin: 0;\n}'; + const ctx = make_context(ours, prettier, 'css'); + const match = run_pattern('css_selector_divergence', ctx); + assertNotEquals(match, null); + } +); + +Deno.test( + 'css_selector_divergence: negative - selector content actually differs (not pure re-indent)', + () => { + // Same shape, but a selector token changed (`ul` → `ol`): NOT a pure re-indent, + // so the pseudo-args clause must not claim it (a real content difference). + const prettier = + ':where(\n\t:is(\n\t\t\tp,\n\t\t\tul\n\t):not(:last-child)\n) {\n\tmargin: 0;\n}'; + const ours = ':where(\n\t:is(\n\t\tp,\n\t\tol\n\t):not(:last-child)\n) {\n\tmargin: 0;\n}'; + const ctx = make_context(ours, prettier, 'css'); + const match = run_pattern('css_selector_divergence', ctx); + assertEquals(match, null); + } +); // ─── forced_continuation_indent ───────────────────────────────────────────── -Deno.test('forced_continuation_indent: positive - type after colon line comment indents one level', () => { - const prettier = 'const e: // c\nFoo = x;'; - const ours = 'const e: // c\n\tFoo = x;'; - const ctx = make_context(ours, prettier, 'svelte'); - const match = run_pattern('forced_continuation_indent', ctx); - assertNotEquals(match, null); -}); - -Deno.test('forced_continuation_indent: positive - module header gap (import keyword→source)', () => { - const prettier = "import // c\n'a';"; - const ours = "import // c\n\t'a';"; - const ctx = make_context(ours, prettier, 'typescript'); - const match = run_pattern('forced_continuation_indent', ctx); - assertNotEquals(match, null); -}); +Deno.test( + 'forced_continuation_indent: positive - type after colon line comment indents one level', + () => { + const prettier = 'const e: // c\nFoo = x;'; + const ours = 'const e: // c\n\tFoo = x;'; + const ctx = make_context(ours, prettier, 'svelte'); + const match = run_pattern('forced_continuation_indent', ctx); + assertNotEquals(match, null); + } +); + +Deno.test( + 'forced_continuation_indent: positive - module header gap (import keyword→source)', + () => { + const prettier = "import // c\n'a';"; + const ours = "import // c\n\t'a';"; + const ctx = make_context(ours, prettier, 'typescript'); + const match = run_pattern('forced_continuation_indent', ctx); + assertNotEquals(match, null); + } +); Deno.test('forced_continuation_indent: positive - prefix type operator operand hang', () => { const prettier = 'type A = keyof // c\nB;'; @@ -1194,23 +1281,29 @@ Deno.test('forced_continuation_indent: positive - prefix type operator operand h assertNotEquals(match, null); }); -Deno.test('forced_continuation_indent: positive - key→colon gap (continuation leads with `:`)', () => { - const prettier = 'interface A {\n\t[\n\t\tkey // c\n\t\t: string\n\t]: number;\n}'; - const ours = 'interface A {\n\t[\n\t\tkey // c\n\t\t\t: string\n\t]: number;\n}'; - const ctx = make_context(ours, prettier, 'typescript'); - const match = run_pattern('forced_continuation_indent', ctx); - assertNotEquals(match, null); -}); - -Deno.test('forced_continuation_indent: positive - block comment sits in the target→colon gap', () => { - // `/* */` never runs to end-of-line, so it cannot force the break — it only - // separates the annotation target from its colon, which still carries the `//`. - const prettier = 'type T = {\n\t[k: string] /* x */ : // c\n\tnumber;\n};'; - const ours = 'type T = {\n\t[k: string] /* x */ : // c\n\t\tnumber;\n};'; - const ctx = make_context(ours, prettier, 'typescript'); - const match = run_pattern('forced_continuation_indent', ctx); - assertNotEquals(match, null); -}); +Deno.test( + 'forced_continuation_indent: positive - key→colon gap (continuation leads with `:`)', + () => { + const prettier = 'interface A {\n\t[\n\t\tkey // c\n\t\t: string\n\t]: number;\n}'; + const ours = 'interface A {\n\t[\n\t\tkey // c\n\t\t\t: string\n\t]: number;\n}'; + const ctx = make_context(ours, prettier, 'typescript'); + const match = run_pattern('forced_continuation_indent', ctx); + assertNotEquals(match, null); + } +); + +Deno.test( + 'forced_continuation_indent: positive - block comment sits in the target→colon gap', + () => { + // `/* */` never runs to end-of-line, so it cannot force the break — it only + // separates the annotation target from its colon, which still carries the `//`. + const prettier = 'type T = {\n\t[k: string] /* x */ : // c\n\tnumber;\n};'; + const ours = 'type T = {\n\t[k: string] /* x */ : // c\n\t\tnumber;\n};'; + const ctx = make_context(ours, prettier, 'typescript'); + const match = run_pattern('forced_continuation_indent', ctx); + assertNotEquals(match, null); + } +); Deno.test('forced_continuation_indent: positive - declaration header gap (keyword→name)', () => { const prettier = 'function // c\nf1() {}'; @@ -1220,48 +1313,60 @@ Deno.test('forced_continuation_indent: positive - declaration header gap (keywor assertNotEquals(match, null); }); -Deno.test('forced_continuation_indent: positive - generator `*` sits between keyword and comment', () => { - const prettier = 'function* // c\ng1() {}'; - const ours = 'function* // c\n\tg1() {}'; - const ctx = make_context(ours, prettier, 'typescript'); - const match = run_pattern('forced_continuation_indent', ctx); - assertNotEquals(match, null); -}); - -Deno.test('forced_continuation_indent: negative - body-open-brace comment is a different gap', () => { - // The keyword must IMMEDIATELY precede the comment. Here the comment trails the - // body's `{`, so it never split a keyword from its name — claiming it would let - // the clause absorb any re-indent under any function. - const prettier = 'function f() { // c\nbar;\n}'; - const ours = 'function f() { // c\n\tbar;\n}'; - const ctx = make_context(ours, prettier, 'typescript'); - const match = run_pattern('forced_continuation_indent', ctx); - assertEquals(match, null); -}); - -Deno.test('forced_continuation_indent: negative - prettier RELOCATES the keyword-gap comment', () => { - // The anonymous class/function expression shape (`expr_anon_line_comment`): the - // head matches the clause's regex, but prettier moves the comment into the body - // rather than merely flattening the indent, so `is_pure_reindent` must reject it. - // That relocation is `comment_position`'s to claim, not this pattern's. - const prettier = 'const a = class {\n\t// c1\n};'; - const ours = 'const a = class // c1\n{};'; - const ctx = make_context(ours, prettier, 'typescript'); - const match = run_pattern('forced_continuation_indent', ctx); - assertEquals(match, null); -}); - -Deno.test('forced_continuation_indent: negative - contextual keyword is not a declaration head', () => { - // The clause's keyword set is closed to the RESERVED words. With the name on the - // next line `interface` is not a declaration keyword at all — Svelte's parser and - // prettier both reject this input — so no such divergence exists to explain, and - // a clause claiming one would be keying on a construct that cannot occur. - const prettier = 'interface // c\nX {}'; - const ours = 'interface // c\n\tX {}'; - const ctx = make_context(ours, prettier, 'typescript'); - const match = run_pattern('forced_continuation_indent', ctx); - assertEquals(match, null); -}); +Deno.test( + 'forced_continuation_indent: positive - generator `*` sits between keyword and comment', + () => { + const prettier = 'function* // c\ng1() {}'; + const ours = 'function* // c\n\tg1() {}'; + const ctx = make_context(ours, prettier, 'typescript'); + const match = run_pattern('forced_continuation_indent', ctx); + assertNotEquals(match, null); + } +); + +Deno.test( + 'forced_continuation_indent: negative - body-open-brace comment is a different gap', + () => { + // The keyword must IMMEDIATELY precede the comment. Here the comment trails the + // body's `{`, so it never split a keyword from its name — claiming it would let + // the clause absorb any re-indent under any function. + const prettier = 'function f() { // c\nbar;\n}'; + const ours = 'function f() { // c\n\tbar;\n}'; + const ctx = make_context(ours, prettier, 'typescript'); + const match = run_pattern('forced_continuation_indent', ctx); + assertEquals(match, null); + } +); + +Deno.test( + 'forced_continuation_indent: negative - prettier RELOCATES the keyword-gap comment', + () => { + // The anonymous class/function expression shape (`expr_anon_line_comment`): the + // head matches the clause's regex, but prettier moves the comment into the body + // rather than merely flattening the indent, so `is_pure_reindent` must reject it. + // That relocation is `comment_position`'s to claim, not this pattern's. + const prettier = 'const a = class {\n\t// c1\n};'; + const ours = 'const a = class // c1\n{};'; + const ctx = make_context(ours, prettier, 'typescript'); + const match = run_pattern('forced_continuation_indent', ctx); + assertEquals(match, null); + } +); + +Deno.test( + 'forced_continuation_indent: negative - contextual keyword is not a declaration head', + () => { + // The clause's keyword set is closed to the RESERVED words. With the name on the + // next line `interface` is not a declaration keyword at all — Svelte's parser and + // prettier both reject this input — so no such divergence exists to explain, and + // a clause claiming one would be keying on a construct that cannot occur. + const prettier = 'interface // c\nX {}'; + const ours = 'interface // c\n\tX {}'; + const ctx = make_context(ours, prettier, 'typescript'); + const match = run_pattern('forced_continuation_indent', ctx); + assertEquals(match, null); + } +); Deno.test('forced_continuation_indent: negative - ternary branch (line-leading colon)', () => { // A `:` at the start of its line is a ternary branch, not an annotation target; @@ -1285,50 +1390,62 @@ Deno.test('forced_continuation_indent: negative - pure re-indent with no comment assertEquals(match, null); }); -Deno.test('forced_continuation_indent: negative - our continuation is two levels below the head', () => { - // The rule indents "one level" below the construct head. A deeper continuation - // is some other layout difference and must not ride along. - const prettier = "import // c\n'a';"; - const ours = "import // c\n\t\t'a';"; - const ctx = make_context(ours, prettier, 'typescript'); - const match = run_pattern('forced_continuation_indent', ctx); - assertEquals(match, null); -}); - -Deno.test('forced_continuation_indent: positive - multi-line continuation shifts as a block', () => { - // An intersection hangs its members a further level in, so the continuation is - // multi-line with internal structure. tsv re-roots the whole block one level - // below the head; a rule demanding every line sit AT head+1 would reject the - // case the pattern was originally written for (and did, on real corpus files). - const prettier = 'elements: // c\n[string] &\n\t[string, B] &\n\t[string, B, C];'; - const ours = 'elements: // c\n\t[string] &\n\t\t[string, B] &\n\t\t[string, B, C];'; - const ctx = make_context(ours, prettier, 'typescript'); - const match = run_pattern('forced_continuation_indent', ctx); - assertNotEquals(match, null); -}); - -Deno.test('forced_continuation_indent: positive - depth is judged against the head, not prettier', () => { - // Prettier's own line is the DIVERGENCE, so it cannot also be the baseline. Here - // prettier emits a tab-plus-stray-space and ours sits exactly one level below the - // `export` head — the rule is satisfied, whatever prettier chose. - const prettier = 'export // c\n {};'; - const ours = 'export // c\n\t{};'; - const ctx = make_context(ours, prettier, 'typescript'); - const match = run_pattern('forced_continuation_indent', ctx); - assertNotEquals(match, null); -}); - -Deno.test('forced_continuation_indent: negative - own-line comment above an unrelated re-indent', () => { - // An own-line comment is not evidence that a comment forced the continuation: - // where one genuinely does lead a continuation, prettier relocates the comment - // and the hunk stops being a pure re-indent (so `comment_position` claims it). - // A bare comment sitting above some other re-indent must not be claimed here. - const prettier = 'const a = 1;\n// c\nfoo(\n\tbar\n);'; - const ours = 'const a = 1;\n// c\nfoo(\n\t\tbar\n);'; - const ctx = make_context(ours, prettier, 'typescript'); - const match = run_pattern('forced_continuation_indent', ctx); - assertEquals(match, null); -}); +Deno.test( + 'forced_continuation_indent: negative - our continuation is two levels below the head', + () => { + // The rule indents "one level" below the construct head. A deeper continuation + // is some other layout difference and must not ride along. + const prettier = "import // c\n'a';"; + const ours = "import // c\n\t\t'a';"; + const ctx = make_context(ours, prettier, 'typescript'); + const match = run_pattern('forced_continuation_indent', ctx); + assertEquals(match, null); + } +); + +Deno.test( + 'forced_continuation_indent: positive - multi-line continuation shifts as a block', + () => { + // An intersection hangs its members a further level in, so the continuation is + // multi-line with internal structure. tsv re-roots the whole block one level + // below the head; a rule demanding every line sit AT head+1 would reject the + // case the pattern was originally written for (and did, on real corpus files). + const prettier = 'elements: // c\n[string] &\n\t[string, B] &\n\t[string, B, C];'; + const ours = 'elements: // c\n\t[string] &\n\t\t[string, B] &\n\t\t[string, B, C];'; + const ctx = make_context(ours, prettier, 'typescript'); + const match = run_pattern('forced_continuation_indent', ctx); + assertNotEquals(match, null); + } +); + +Deno.test( + 'forced_continuation_indent: positive - depth is judged against the head, not prettier', + () => { + // Prettier's own line is the DIVERGENCE, so it cannot also be the baseline. Here + // prettier emits a tab-plus-stray-space and ours sits exactly one level below the + // `export` head — the rule is satisfied, whatever prettier chose. + const prettier = 'export // c\n {};'; + const ours = 'export // c\n\t{};'; + const ctx = make_context(ours, prettier, 'typescript'); + const match = run_pattern('forced_continuation_indent', ctx); + assertNotEquals(match, null); + } +); + +Deno.test( + 'forced_continuation_indent: negative - own-line comment above an unrelated re-indent', + () => { + // An own-line comment is not evidence that a comment forced the continuation: + // where one genuinely does lead a continuation, prettier relocates the comment + // and the hunk stops being a pure re-indent (so `comment_position` claims it). + // A bare comment sitting above some other re-indent must not be claimed here. + const prettier = 'const a = 1;\n// c\nfoo(\n\tbar\n);'; + const ours = 'const a = 1;\n// c\nfoo(\n\t\tbar\n);'; + const ctx = make_context(ours, prettier, 'typescript'); + const match = run_pattern('forced_continuation_indent', ctx); + assertEquals(match, null); + } +); Deno.test('forced_continuation_indent: negative - content changed, not a pure re-indent', () => { // `is_pure_reindent` is the content-preservation proof the whole detector rests @@ -1592,15 +1709,18 @@ Deno.test('template_embedded_verbatim: positive - css body expanded by prettier' assertNotEquals(match, null); }); -Deno.test('template_embedded_verbatim: negative - unrecognized tag (no embedded formatting)', () => { - // `sql` is not an embedded-language tag prettier reformats, so a diff on a - // `sql`…`` template must not be claimed. - const ours = '\tconst q = sql`SELECT 1`;'; - const prettier = '\tconst q = sql`SELECT 2`;'; - const ctx = make_context(ours, prettier, 'typescript'); - const match = run_pattern('template_embedded_verbatim', ctx); - assertEquals(match, null); -}); +Deno.test( + 'template_embedded_verbatim: negative - unrecognized tag (no embedded formatting)', + () => { + // `sql` is not an embedded-language tag prettier reformats, so a diff on a + // `sql`…`` template must not be claimed. + const ours = '\tconst q = sql`SELECT 1`;'; + const prettier = '\tconst q = sql`SELECT 2`;'; + const ctx = make_context(ours, prettier, 'typescript'); + const match = run_pattern('template_embedded_verbatim', ctx); + assertEquals(match, null); + } +); // ─── field_key_unquote ──────────────────────────────────────────────────── @@ -1959,17 +2079,20 @@ Deno.test('jsdoc_type_cast_parens: positive - svelte lang=ts (ours keeps, pretti // must REJECT it — claiming would mask a bug, and in corpus_compare_format can mask a // data-loss safety violation by reclassifying it as known_divergence. -Deno.test('member_expression_call: negative - bug collapses what prettier expanded (no re-wrap)', () => { - // `require.resolve(` is present, but ours COLLAPSED the call onto one line - // while prettier kept it expanded — the opposite of the documented divergence - // (ours expands). added_lines.length is not > removed_lines.length → reject. - const prettier = 'const p = require.resolve(\n\t"x",\n);'; - const ours = 'const p = require.resolve("x");'; - const ctx = make_context(ours, prettier, 'typescript'); - ctx.source = 'const p = require.resolve("x");'; - const match = run_pattern('member_expression_call', ctx); - assertEquals(match, null); -}); +Deno.test( + 'member_expression_call: negative - bug collapses what prettier expanded (no re-wrap)', + () => { + // `require.resolve(` is present, but ours COLLAPSED the call onto one line + // while prettier kept it expanded — the opposite of the documented divergence + // (ours expands). added_lines.length is not > removed_lines.length → reject. + const prettier = 'const p = require.resolve(\n\t"x",\n);'; + const ours = 'const p = require.resolve("x");'; + const ctx = make_context(ours, prettier, 'typescript'); + ctx.source = 'const p = require.resolve("x");'; + const match = run_pattern('member_expression_call', ctx); + assertEquals(match, null); + } +); Deno.test('member_expression_call: negative - module pattern only on prettier side', () => { // The module pattern appears only in prettier's removed lines; ours rewrote @@ -2060,52 +2183,63 @@ Deno.test('css_scss_directive_number: negative - dropped numeric value not claim // `prettier/tests/format/html/tags/tags.html` two unrelated whitespace hunks were // load-bearing for a delta caused entirely by a third. -Deno.test('safety_vouched: a char-risky hunk claimed only by a NON-vouching pattern is not vouched', () => { - // `comment_position` does not declare `may_alter_char_frequency` (it relocates a - // comment, it does not add or drop content), so it cannot excuse a char delta. - const prettier = '
\n\t\n\ta\n
'; - const ours = '
\n\ta\n\t\n
'; - const ctx = make_context(ours, prettier, 'svelte'); - const coverage = detect_divergences(ctx); - // Whatever it claims, no vouching pattern exists here, so any char-risky hunk - // leaves the coverage unvouched. - if (coverage.char_risky_hunks.length > 0) { - assertEquals(coverage.safety_vouched, false); +Deno.test( + 'safety_vouched: a char-risky hunk claimed only by a NON-vouching pattern is not vouched', + () => { + // `comment_position` does not declare `may_alter_char_frequency` (it relocates a + // comment, it does not add or drop content), so it cannot excuse a char delta. + const prettier = '
\n\t\n\ta\n
'; + const ours = '
\n\ta\n\t\n
'; + const ctx = make_context(ours, prettier, 'svelte'); + const coverage = detect_divergences(ctx); + // Whatever it claims, no vouching pattern exists here, so any char-risky hunk + // leaves the coverage unvouched. + if (coverage.char_risky_hunks.length > 0) { + assertEquals(coverage.safety_vouched, false); + } } -}); - -Deno.test('safety_vouched: a whitespace-only hunk is never char-risky, so it need not vouch', () => { - // Reflowing whitespace cannot move the semantic char count, so such a hunk is - // excluded from the vouching requirement entirely — which is the whole point: - // it can no longer prop up (or, by regressing, collapse) a SAFETY downgrade. - const prettier = '
\n\ta\n
'; - const ours = '
\n a\n
'; - const ctx = make_context(ours, prettier, 'svelte'); - const coverage = detect_divergences(ctx); - assertEquals(coverage.char_risky_hunks, []); -}); - -Deno.test('safety_vouched: self_closing_nonvoid vouches for the hunk carrying its own char delta', () => { - // `` → `` adds `<`, `/`, `>` and the tag name — a real char delta, and - // exactly the tags.html case. The pattern declares `may_alter_char_frequency`, so - // it may vouch for the hunk it claims. - const prettier = '
\n\t\n
'; - const ours = '
\n\t\n
'; - const ctx = make_context(ours, prettier, 'svelte'); - const coverage = detect_divergences(ctx); - assertEquals(coverage.char_risky_hunks.length > 0, true); - assertEquals(coverage.safety_vouched, true); -}); +); + +Deno.test( + 'safety_vouched: a whitespace-only hunk is never char-risky, so it need not vouch', + () => { + // Reflowing whitespace cannot move the semantic char count, so such a hunk is + // excluded from the vouching requirement entirely — which is the whole point: + // it can no longer prop up (or, by regressing, collapse) a SAFETY downgrade. + const prettier = '
\n\ta\n
'; + const ours = '
\n a\n
'; + const ctx = make_context(ours, prettier, 'svelte'); + const coverage = detect_divergences(ctx); + assertEquals(coverage.char_risky_hunks, []); + } +); + +Deno.test( + 'safety_vouched: self_closing_nonvoid vouches for the hunk carrying its own char delta', + () => { + // `` → `` adds `<`, `/`, `>` and the tag name — a real char delta, and + // exactly the tags.html case. The pattern declares `may_alter_char_frequency`, so + // it may vouch for the hunk it claims. + const prettier = '
\n\t\n
'; + const ours = '
\n\t\n
'; + const ctx = make_context(ours, prettier, 'svelte'); + const coverage = detect_divergences(ctx); + assertEquals(coverage.char_risky_hunks.length > 0, true); + assertEquals(coverage.safety_vouched, true); + } +); Deno.test('may_alter_char_frequency: only deliberately-declared patterns may vouch', () => { // The declaration is a promise that the pattern's detect carries a // content-preservation proof. Keep the set small and reviewed — a new pattern is // safe by omission (the field defaults to false, so the gate fails CLOSED). - const vouching = PATTERNS.filter((p) => p.may_alter_char_frequency).map((p) => p.id).sort(); + const vouching = PATTERNS.filter((p) => p.may_alter_char_frequency) + .map((p) => p.id) + .sort(); assertEquals(vouching, [ 'bom_strip', 'comment_preserved', 'css_scss_directive_number', - 'self_closing_nonvoid', + 'self_closing_nonvoid' ]); }); diff --git a/benches/js/lib/divergence/safety.ts b/benches/js/lib/divergence/safety.ts index 362bf3d2a..aa70f9314 100644 --- a/benches/js/lib/divergence/safety.ts +++ b/benches/js/lib/divergence/safety.ts @@ -96,7 +96,7 @@ const FORMATTING_CHARS = new Set([ ';', // Parens (optional in arrow params, some grouping) '(', - ')', + ')' ]); /** @@ -131,7 +131,7 @@ const FORMATTING_CHARS = new Set([ export function check_safety_vs_prettier( source: string, ours: string, - prettier: string, + prettier: string ): SafetyViolation[] { const source_counts = count_semantic_chars(source); const ours_counts = count_semantic_chars(ours); @@ -141,11 +141,11 @@ export function check_safety_vs_prettier( // remainder our output incurs beyond prettier. const lost = differential_deltas( excess_chars(source_counts, ours_counts), - excess_chars(source_counts, prettier_counts), + excess_chars(source_counts, prettier_counts) ); const added = differential_deltas( excess_chars(ours_counts, source_counts), - excess_chars(prettier_counts, source_counts), + excess_chars(prettier_counts, source_counts) ); const violations: SafetyViolation[] = []; @@ -160,10 +160,7 @@ export function check_safety_vs_prettier( * Per-char positive excess of `a` over `b`: `{char => a[char] - b[char]}` for * every char where `a` has strictly more than `b`. */ -function excess_chars( - a: Map, - b: Map, -): Map { +function excess_chars(a: Map, b: Map): Map { const out = new Map(); for (const [char, a_count] of a) { const b_count = b.get(char) ?? 0; @@ -180,7 +177,7 @@ function excess_chars( */ function differential_deltas( ours_excess: Map, - prettier_excess: Map, + prettier_excess: Map ): SafetyCharDelta[] { const out: SafetyCharDelta[] = []; for (const [char, ours] of ours_excess) { @@ -203,7 +200,7 @@ function make_violation( type: SafetyViolation['type'], chars: SafetyCharDelta[], scan_text: string, - other_text: string, + other_text: string ): SafetyViolation { const total = chars.reduce((sum, d) => sum + d.real, 0); const real_map = new Map(chars.map((d) => [d.char, d.real])); @@ -233,7 +230,7 @@ function format_summary(total: number, chars: SafetyCharDelta[]): string { function find_missing_lines( source: string, formatted: string, - lost_chars: Map, + lost_chars: Map ): string[] { const source_lines = source.split('\n'); const formatted_normalized = normalize_for_comparison(formatted); diff --git a/benches/js/lib/divergence/safety_test.ts b/benches/js/lib/divergence/safety_test.ts index fdd08ac11..c5e184dcd 100644 --- a/benches/js/lib/divergence/safety_test.ts +++ b/benches/js/lib/divergence/safety_test.ts @@ -182,13 +182,16 @@ Deno.test('vs_prettier: prettier-empty output never fabricates a violation', () assertEquals(check_safety_vs_prettier(source, 'const x = abc;\n', ''), []); // ours formatted }); -Deno.test('vs_prettier: prettier-empty MASKS a real loss (false negative — caller must guard)', () => { - // The flip side: when prettier is empty AND ours genuinely drops a char, the - // differential subtracts the loss away (prettier "dropped" it too) and reports - // nothing. The primitive cannot tell real loss from a sidecar miss once - // prettier is gone — which is why `corpus_compare_format.ts` errors out on - // `prettier === '' && source non-empty` instead of trusting this verdict. - const source = 'const x = abc;\n'; - const ours = 'const x = ab;\n'; // ours dropped the `c` - assertEquals(check_safety_vs_prettier(source, ours, ''), []); // masked — NOT flagged -}); +Deno.test( + 'vs_prettier: prettier-empty MASKS a real loss (false negative — caller must guard)', + () => { + // The flip side: when prettier is empty AND ours genuinely drops a char, the + // differential subtracts the loss away (prettier "dropped" it too) and reports + // nothing. The primitive cannot tell real loss from a sidecar miss once + // prettier is gone — which is why `corpus_compare_format.ts` errors out on + // `prettier === '' && source non-empty` instead of trusting this verdict. + const source = 'const x = abc;\n'; + const ours = 'const x = ab;\n'; // ours dropped the `c` + assertEquals(check_safety_vs_prettier(source, ours, ''), []); // masked — NOT flagged + } +); diff --git a/benches/js/lib/divergence/validation.ts b/benches/js/lib/divergence/validation.ts index 1ddbd1363..7ffa8a476 100644 --- a/benches/js/lib/divergence/validation.ts +++ b/benches/js/lib/divergence/validation.ts @@ -219,10 +219,11 @@ export function parse_conformance_prettier_md(content: string): DocumentedDiverg // List item: feature is the text between the bullet and the // em-dash before the link (falls back to the fixture name) const before_fixture = line.slice(0, fixture_match.index); - feature = before_fixture - .replace(/^\s*[-*]\s*/, '') - .replace(/\s*[—–]\s*$/, '') - .trim() || fixture_name; + feature = + before_fixture + .replace(/^\s*[-*]\s*/, '') + .replace(/\s*[—–]\s*$/, '') + .trim() || fixture_name; } else { // Prose paragraph: use the bold prefix when present const bold_match = line.match(/^\*\*([^*]+)\*\*/); @@ -234,7 +235,7 @@ export function parse_conformance_prettier_md(content: string): DocumentedDiverg feature, reason, fixture_name, - fixture_path, + fixture_path }); } } @@ -332,7 +333,7 @@ async function detect_fixture(fixture_path: string): Promise { fixture_path, status: best, patterns: [...claiming].sort(), - ...(best === 'partial' ? { unexplained_hunks: unexplained } : {}), + ...(best === 'partial' ? { unexplained_hunks: unexplained } : {}) }; } @@ -391,7 +392,7 @@ export async function generate_audit_report(): Promise { documented_fixtures: documented_in_claimed, claimed_fixtures: claimed, undocumented_fixtures: undocumented_in_claimed, - detected_fixtures: detected_by_pattern.get(pattern.id) ?? [], + detected_fixtures: detected_by_pattern.get(pattern.id) ?? [] }; }); @@ -420,14 +421,14 @@ export async function generate_audit_report(): Promise { total_partial: partial_fixtures.length, total_undetected: undetected_fixtures.length, total_ungradeable: ungradeable_fixtures.length, - coverage_percent: documented_paths.size > 0 - ? Math.round((explained_fixtures.length / documented_paths.size) * 100) - : 100, - gradeable_percent: gradeable > 0 - ? Math.round((explained_fixtures.length / gradeable) * 100) - : 100, + coverage_percent: + documented_paths.size > 0 + ? Math.round((explained_fixtures.length / documented_paths.size) * 100) + : 100, + gradeable_percent: + gradeable > 0 ? Math.round((explained_fixtures.length / gradeable) * 100) : 100, total_listed: listed_documented.length, - total_unlisted_but_explained: unlisted_but_explained.length, + total_unlisted_but_explained: unlisted_but_explained.length }; return { @@ -441,7 +442,7 @@ export async function generate_audit_report(): Promise { pattern_coverage, orphaned_pattern_fixtures, missing_pattern_fixtures, - stats, + stats }; } @@ -463,7 +464,9 @@ export function format_audit_report(report: AuditReport): string { lines.push(`Partial (hunks left): ${s.total_partial}`); lines.push(`Undetected (real gaps): ${s.total_undetected}`); lines.push(`Ungradeable: ${s.total_ungradeable} (pin no prettier form to test)`); - lines.push(`Detection: ${s.coverage_percent}% (${s.gradeable_percent}% of gradeable)`); + lines.push( + `Detection: ${s.coverage_percent}% (${s.gradeable_percent}% of gradeable)` + ); lines.push(''); lines.push(`Listed in fixtures[]: ${s.total_listed}`); lines.push(`Explained but unlisted: ${s.total_unlisted_but_explained} (bookkeeping, not a gap)`); @@ -490,7 +493,7 @@ export function format_audit_report(report: AuditReport): string { const left = detected?.unexplained_hunks; lines.push( ` - ${f.fixture_name}${f.reason ? ` (${f.reason})` : ''}` + - (left ? ` [${left} hunk(s) unexplained]` : ''), + (left ? ` [${left} hunk(s) unexplained]` : '') ); lines.push(` ${f.fixture_path}`); } @@ -503,11 +506,11 @@ export function format_audit_report(report: AuditReport): string { // so it reads as covered until you count hunks. push_by_section( 'Undetected Fixtures (pin a prettier form, no pattern explains it):', - report.undetected_fixtures, + report.undetected_fixtures ); push_by_section( 'Partially Explained Fixtures (some hunks claimed, some left over):', - report.partial_fixtures, + report.partial_fixtures ); // Ungradeable — neither a success nor a gap; listed so the number is legible @@ -560,9 +563,9 @@ export function format_audit_report(report: AuditReport): string { const listed = pc.claimed_fixtures.length; const detects = pc.detected_fixtures.length; lines.push( - ` ${pc.pattern_id.padEnd(34)} listed ${String(listed).padStart(3)} detects ${ - String(detects).padStart(3) - }`, + ` ${pc.pattern_id.padEnd(34)} listed ${String(listed).padStart(3)} detects ${String( + detects + ).padStart(3)}` ); } diff --git a/benches/js/lib/dprint.ts b/benches/js/lib/dprint.ts index 821402efe..22ebfc431 100644 --- a/benches/js/lib/dprint.ts +++ b/benches/js/lib/dprint.ts @@ -76,7 +76,7 @@ export class DprintImplementation implements TsvImplementation { // `singleQuote: true`, which still switches quotes to avoid escaping. this._formatter.setConfig( { lineWidth: 100, indentWidth: 2, useTabs: true }, - { quoteStyle: 'preferSingle', trailingCommas: 'never' }, + { quoteStyle: 'preferSingle', trailingCommas: 'never' } ); // Assert the config actually LANDED. dprint reports an unrecognized key as @@ -119,7 +119,7 @@ export class DprintImplementation implements TsvImplementation { // synthetic-filepath treatment lib/biome.ts gives the language. return this._formatter.formatText({ filePath: `file${LANGUAGE_EXTENSIONS[language]}`, - fileText: source, + fileText: source }); } diff --git a/benches/js/lib/ffi.ts b/benches/js/lib/ffi.ts index 6c17dbca2..4fe1e79ff 100644 --- a/benches/js/lib/ffi.ts +++ b/benches/js/lib/ffi.ts @@ -34,80 +34,80 @@ import type { Language, ParseGoal, TsvImplementation } from './types.ts'; const symbols = { tsv_parse_svelte: { parameters: ['pointer', 'usize', 'pointer'], - result: 'pointer', + result: 'pointer' }, tsv_parse_internal_svelte: { parameters: ['pointer', 'usize', 'pointer'], - result: 'pointer', + result: 'pointer' }, tsv_format_svelte: { parameters: ['pointer', 'usize', 'pointer'], - result: 'pointer', + result: 'pointer' }, tsv_parse_typescript: { parameters: ['pointer', 'usize', 'pointer'], - result: 'pointer', + result: 'pointer' }, tsv_parse_internal_typescript: { parameters: ['pointer', 'usize', 'pointer'], - result: 'pointer', + result: 'pointer' }, tsv_format_typescript: { parameters: ['pointer', 'usize', 'pointer'], - result: 'pointer', + result: 'pointer' }, tsv_parse_css: { parameters: ['pointer', 'usize', 'pointer'], - result: 'pointer', + result: 'pointer' }, tsv_parse_internal_css: { parameters: ['pointer', 'usize', 'pointer'], - result: 'pointer', + result: 'pointer' }, // no-locations parse (span-only wire) — svelte + typescript only (CSS emits no `loc`) tsv_parse_svelte_no_locations: { parameters: ['pointer', 'usize', 'pointer'], - result: 'pointer', + result: 'pointer' }, tsv_parse_typescript_no_locations: { parameters: ['pointer', 'usize', 'pointer'], - result: 'pointer', + result: 'pointer' }, // goal-aware TS parse (extra `u32` goal: 0 = Module, 1 = Script) — the // conformance surface's test262 files tsv_parse_typescript_with_goal: { parameters: ['pointer', 'usize', 'u32', 'pointer'], - result: 'pointer', + result: 'pointer' }, tsv_parse_typescript_no_locations_with_goal: { parameters: ['pointer', 'usize', 'u32', 'pointer'], - result: 'pointer', + result: 'pointer' }, tsv_parse_internal_typescript_with_goal: { parameters: ['pointer', 'usize', 'u32', 'pointer'], - result: 'pointer', + result: 'pointer' }, tsv_format_css: { parameters: ['pointer', 'usize', 'pointer'], - result: 'pointer', + result: 'pointer' }, tsv_free: { parameters: ['pointer', 'usize'], - result: 'void', - }, + result: 'void' + } } as const; type FfiFn = ( source: Deno.PointerValue, len: number | bigint, - out_len: Deno.PointerValue, + out_len: Deno.PointerValue ) => Deno.PointerValue; /** Goal-aware TS parse symbol: an extra `u32` goal (0 = Module, 1 = Script). */ type FfiGoalFn = ( source: Deno.PointerValue, len: number | bigint, goal: number, - out_len: Deno.PointerValue, + out_len: Deno.PointerValue ) => Deno.PointerValue; type LibSymbols = Deno.DynamicLibrary['symbols']; @@ -177,7 +177,7 @@ export class NativeImplementation implements TsvImplementation { `Native library not found at ${lib_path}. ` + `Run 'cargo build -p tsv_ffi --${ profile === 'release' ? 'release' : `profile ${profile}` - }' first.`, + }' first.` ); } @@ -194,7 +194,7 @@ export class NativeImplementation implements TsvImplementation { out_len_ptr: Deno.UnsafePointer.of(out_len_buffer), source_buffer, source_ptr: Deno.UnsafePointer.of(source_buffer), - result_buffer: new Uint8Array(new ArrayBuffer(INITIAL_BUFFER_CAPACITY)), + result_buffer: new Uint8Array(new ArrayBuffer(INITIAL_BUFFER_CAPACITY)) }; } @@ -208,11 +208,11 @@ export class NativeImplementation implements TsvImplementation { const max_bytes = source.length * 3; if (max_bytes > m.source_buffer.length) { m.source_buffer = new Uint8Array( - new ArrayBuffer(next_capacity(max_bytes, m.source_buffer.length)), + new ArrayBuffer(next_capacity(max_bytes, m.source_buffer.length)) ); m.source_ptr = Deno.UnsafePointer.of(m.source_buffer); } - const {read, written} = this.encoder.encodeInto(source, m.source_buffer); + const { read, written } = this.encoder.encodeInto(source, m.source_buffer); if (read !== source.length) { throw new Error(`encodeInto consumed ${read} of ${source.length} source units`); } @@ -227,7 +227,7 @@ export class NativeImplementation implements TsvImplementation { const result_byte_count = Number(result_len); if (result_byte_count > m.result_buffer.length) { m.result_buffer = new Uint8Array( - new ArrayBuffer(next_capacity(result_byte_count, m.result_buffer.length)), + new ArrayBuffer(next_capacity(result_byte_count, m.result_buffer.length)) ); } @@ -253,11 +253,11 @@ export class NativeImplementation implements TsvImplementation { const max_bytes = source.length * 3; if (max_bytes > m.source_buffer.length) { m.source_buffer = new Uint8Array( - new ArrayBuffer(next_capacity(max_bytes, m.source_buffer.length)), + new ArrayBuffer(next_capacity(max_bytes, m.source_buffer.length)) ); m.source_ptr = Deno.UnsafePointer.of(m.source_buffer); } - const {read, written} = this.encoder.encodeInto(source, m.source_buffer); + const { read, written } = this.encoder.encodeInto(source, m.source_buffer); if (read !== source.length) { throw new Error(`encodeInto consumed ${read} of ${source.length} source units`); } @@ -271,7 +271,7 @@ export class NativeImplementation implements TsvImplementation { const result_byte_count = Number(result_len); if (result_byte_count > m.result_buffer.length) { m.result_buffer = new Uint8Array( - new ArrayBuffer(next_capacity(result_byte_count, m.result_buffer.length)), + new ArrayBuffer(next_capacity(result_byte_count, m.result_buffer.length)) ); } @@ -315,7 +315,7 @@ export class NativeImplementation implements TsvImplementation { return { svelte: this.symbols.tsv_parse_svelte as FfiFn, typescript: this.symbols.tsv_parse_typescript as FfiFn, - css: this.symbols.tsv_parse_css as FfiFn, + css: this.symbols.tsv_parse_css as FfiFn }; } @@ -323,7 +323,7 @@ export class NativeImplementation implements TsvImplementation { return { svelte: this.symbols.tsv_parse_internal_svelte as FfiFn, typescript: this.symbols.tsv_parse_internal_typescript as FfiFn, - css: this.symbols.tsv_parse_internal_css as FfiFn, + css: this.symbols.tsv_parse_internal_css as FfiFn }; } @@ -331,7 +331,7 @@ export class NativeImplementation implements TsvImplementation { private get parse_no_locations_fns(): Partial> { return { svelte: this.symbols.tsv_parse_svelte_no_locations as FfiFn, - typescript: this.symbols.tsv_parse_typescript_no_locations as FfiFn, + typescript: this.symbols.tsv_parse_typescript_no_locations as FfiFn }; } @@ -339,14 +339,15 @@ export class NativeImplementation implements TsvImplementation { return { svelte: this.symbols.tsv_format_svelte as FfiFn, typescript: this.symbols.tsv_format_typescript as FfiFn, - css: this.symbols.tsv_format_css as FfiFn, + css: this.symbols.tsv_format_css as FfiFn }; } parse(source: string, language: Language, goal?: ParseGoal): unknown { - const result = goal && language === 'typescript' - ? this.call_ffi_goal(this.symbols.tsv_parse_typescript_with_goal as FfiGoalFn, source, goal) - : this.call_ffi(this.parse_fns[language], source); + const result = + goal && language === 'typescript' + ? this.call_ffi_goal(this.symbols.tsv_parse_typescript_with_goal as FfiGoalFn, source, goal) + : this.call_ffi(this.parse_fns[language], source); const parsed = JSON.parse(result); if (parsed.error) { throw new Error(parsed.error); @@ -355,13 +356,14 @@ export class NativeImplementation implements TsvImplementation { } parse_internal(source: string, language: Language, goal?: ParseGoal): void { - const result = goal && language === 'typescript' - ? this.call_ffi_goal( - this.symbols.tsv_parse_internal_typescript_with_goal as FfiGoalFn, - source, - goal, - ) - : this.call_ffi(this.parse_internal_fns[language], source); + const result = + goal && language === 'typescript' + ? this.call_ffi_goal( + this.symbols.tsv_parse_internal_typescript_with_goal as FfiGoalFn, + source, + goal + ) + : this.call_ffi(this.parse_internal_fns[language], source); this.check_error(result); } @@ -371,7 +373,7 @@ export class NativeImplementation implements TsvImplementation { result = this.call_ffi_goal( this.symbols.tsv_parse_typescript_no_locations_with_goal as FfiGoalFn, source, - goal, + goal ); } else { const fn = this.parse_no_locations_fns[language]; diff --git a/benches/js/lib/fixtures_gate.ts b/benches/js/lib/fixtures_gate.ts index e97f5df54..587530824 100644 --- a/benches/js/lib/fixtures_gate.ts +++ b/benches/js/lib/fixtures_gate.ts @@ -28,7 +28,12 @@ import type { GatePins } from './gate_counts.ts'; import type { KnownGap, Sanction } from './parse_sanctions.ts'; import type { Language } from './types.ts'; import { type CanonicalVersions, load_all_versions } from './versions.ts'; -import { bigint_replacer, type DiffEntry, diff_asts, type MatchContext } from '../corpus_compare_parse.ts'; +import { + bigint_replacer, + type DiffEntry, + diff_asts, + type MatchContext +} from '../corpus_compare_parse.ts'; export interface FixturesGateConfig { /** Display title, e.g. `TypeScript-fixtures`. */ @@ -79,7 +84,7 @@ interface AstGroup { async function* discover( root: string, - config: FixturesGateConfig, + config: FixturesGateConfig ): AsyncGenerator<{ path: string; content: string }> { let entries; try { @@ -122,7 +127,7 @@ export async function run_fixtures_gate(config: FixturesGateConfig): Promise(); @@ -141,7 +146,13 @@ export async function run_fixtures_gate(config: FixturesGateConfig): Promise `${c}=${n}`).join(', ') || 'none' - })`, + })` ); console.error(` over-rejection UNEXPECTED: ${buckets.unexpected.length} (new gap — GATES)`); console.error(`\n AST-SHAPE (of the ${buckets.both_accept} both-accept)`); console.error(` clean: ${ast_clean}`); console.error(` documented diff groups: ${documented_groups.length}`); - console.error(` undocumented diff groups: ${undocumented_groups.length} (report-only — triage surface)`); + console.error( + ` undocumented diff groups: ${undocumented_groups.length} (report-only — triage surface)` + ); for (const e of buckets.unexpected) { console.error(`\n ✗ UNEXPECTED over-rejection: ${e.path}\n ${e.error}`); @@ -277,7 +299,7 @@ export async function run_fixtures_gate(config: FixturesGateConfig): Promise ({ signature: g.signature, count: g.count, files: [...g.files].slice(0, 10), - sample: g.sample ? { path: g.sample.path, entry: g.sample.entry } : null, - })), - }, + sample: g.sample ? { path: g.sample.path, entry: g.sample.entry } : null + })) + } }; if (json_mode) { @@ -322,7 +344,7 @@ export async function run_fixtures_gate(config: FixturesGateConfig): Promise !used_sanctions.has(s.pattern)).map((s) => `sanction: ${s.pattern}`), - ...config.known_gaps.filter((g) => !used_gaps.has(g.pattern)).map((g) => `known-gap: ${g.pattern}`), + ...config.sanctioned + .filter((s) => !used_sanctions.has(s.pattern)) + .map((s) => `sanction: ${s.pattern}`), + ...config.known_gaps + .filter((g) => !used_gaps.has(g.pattern)) + .map((g) => `known-gap: ${g.pattern}`) ]; if (stale.length > 0) { console.error( `\nFAIL: ${stale.length} stale ledger entr${stale.length === 1 ? 'y' : 'ies'} — matched no over-rejection in this full-suite run:\n` + stale.map((s) => ` · ${s}`).join('\n') + - `\n Delete the entry if its gap/leniency was fixed; update the pattern if upstream renamed the fixture.`, + `\n Delete the entry if its gap/leniency was fixed; update the pattern if upstream renamed the fixture.` ); Deno.exit(1); } @@ -360,17 +386,15 @@ export async function run_fixtures_gate(config: FixturesGateConfig): Promise f !== null); if (pin_failures.length > 0) { console.error( `\nFAIL: pinned count mismatch — ${pin_failures.join('; ')}. ` + - `If this move is deliberate (suite refresh, behavior change), re-pin in lib/gate_counts.ts (see its update ritual).`, + `If this move is deliberate (suite refresh, behavior change), re-pin in lib/gate_counts.ts (see its update ritual).` ); Deno.exit(1); } diff --git a/benches/js/lib/gate_counts.ts b/benches/js/lib/gate_counts.ts index 1ba213a9a..7ad11af19 100644 --- a/benches/js/lib/gate_counts.ts +++ b/benches/js/lib/gate_counts.ts @@ -88,13 +88,16 @@ import type { Language } from './types.ts'; * Re-record a commit in the same change that re-pins the counts it explains. */ export const GATE_CHECKOUT_COMMITS: Record = { - '../svelte': { commit: 'b4d1583ae', pins: 'SVELTE_FIXTURES_PINS, CORPUS_FORMAT_*, CORPUS_PARSE_*' }, + '../svelte': { + commit: 'b4d1583ae', + pins: 'SVELTE_FIXTURES_PINS, CORPUS_FORMAT_*, CORPUS_PARSE_*' + }, '../acorn-typescript': { commit: '312d079', pins: 'TS_FIXTURES_PINS' }, '../typescript': { commit: '637d5746b', pins: 'TS_REPO_PINS' }, '../kit': { commit: 'da5b08ea7', pins: 'CORPUS_FORMAT_*, CORPUS_PARSE_*' }, '../svelte.dev': { commit: 'c21c2d0f0', pins: 'CORPUS_FORMAT_*, CORPUS_PARSE_*' }, '../prettier': { commit: '1dcd0b05d', pins: 'CORPUS_FORMAT_*, CORPUS_PARSE_*' }, - '../prettier-plugin-svelte': { commit: '7809486', pins: 'CORPUS_FORMAT_*, CORPUS_PARSE_*' }, + '../prettier-plugin-svelte': { commit: '7809486', pins: 'CORPUS_FORMAT_*, CORPUS_PARSE_*' } }; /** Exact expected counts for a fixtures parse-conformance gate (`lib/fixtures_gate.ts`). */ @@ -126,7 +129,7 @@ export const TS_REPO_PINS = { scanned: 768, accept_parity: 429 }; export const CORPUS_PARSE_COMPARED_MIN: Record = { svelte: 1371, typescript: 4356, - css: 168, + css: 168 }; /** @@ -139,7 +142,7 @@ export const CORPUS_PARSE_COMPARED_MIN: Record = { export const CORPUS_PARSE_TSV_ERRORS_PIN: Record = { svelte: 0, typescript: 9, - css: 3, + css: 3 }; /** @@ -157,7 +160,7 @@ export const CORPUS_PARSE_TSV_ERRORS_PIN: Record = { export const CORPUS_FORMAT_MATCH_MIN: Record = { svelte: 513, typescript: 2332, - css: 89, + css: 89 }; /** @@ -173,7 +176,7 @@ export const CORPUS_FORMAT_MATCH_MIN: Record = { export const CORPUS_FORMAT_UNKNOWN_PIN: Record = { svelte: 7, typescript: 115, - css: 23, + css: 23 }; /** @@ -185,7 +188,7 @@ export const CORPUS_FORMAT_UNKNOWN_PIN: Record = { export const CORPUS_FORMAT_PARTIAL_PIN: Record = { svelte: 1, typescript: 44, - css: 9, + css: 9 }; /** diff --git a/benches/js/lib/harvest_stamp.ts b/benches/js/lib/harvest_stamp.ts index fa67f1b34..cf9ba88bd 100644 --- a/benches/js/lib/harvest_stamp.ts +++ b/benches/js/lib/harvest_stamp.ts @@ -30,7 +30,7 @@ export function git_head(repo: string): string | null { const out = new Deno.Command('git', { args: ['-C', repo, 'rev-parse', 'HEAD'], stdout: 'piped', - stderr: 'null', + stderr: 'null' }).outputSync(); if (!out.success) return null; return new TextDecoder().decode(out.stdout).trim(); @@ -54,7 +54,7 @@ export function short_commit(sha: string): string { export async function harvest_up_to_date( stamp_path: string, inputs: StampInputs, - caches: string[], + caches: string[] ): Promise { for (const path of caches) { try { @@ -72,8 +72,7 @@ export async function stamp_fresh(path: string, inputs: StampInputs): Promise recorded[k] === inputs[k]) + keys.length === Object.keys(recorded).length && keys.every((k) => recorded[k] === inputs[k]) ); } catch { return false; diff --git a/benches/js/lib/implementations.ts b/benches/js/lib/implementations.ts index 44b6c58a7..ae0a7e747 100644 --- a/benches/js/lib/implementations.ts +++ b/benches/js/lib/implementations.ts @@ -65,7 +65,7 @@ export interface InitOptions { * ``` */ export async function init_implementations( - options: InitOptions = {}, + options: InitOptions = {} ): Promise { const { logger = console.log, skip_missing = true, require_canonical = true } = options; @@ -194,7 +194,7 @@ export async function init_implementations( oxc: oxc_impl, oxc_wasm: oxc_wasm_impl, biome: biome_impl, - dprint: dprint_impl, + dprint: dprint_impl }; } @@ -237,7 +237,7 @@ export function get_benchmark_tasks( impls: InitializedImplementations, operation: 'parse' | 'format', language: Language, - options: BenchmarkTaskOptions = {}, + options: BenchmarkTaskOptions = {} ): BenchmarkTask[] { const tasks: BenchmarkTask[] = []; const group_name = `${operation}/${language}`; @@ -248,7 +248,7 @@ export function get_benchmark_tasks( name: canonical_parser_label(language), tracking_key: `${group_name}/canonical`, is_async: false, - run: (source, _language, goal) => impls.canonical.parse(source, language, goal), + run: (source, _language, goal) => impls.canonical.parse(source, language, goal) }); // Native parser (with JSON serialization) @@ -257,7 +257,7 @@ export function get_benchmark_tasks( name: 'tsv-json', tracking_key: `${group_name}/native`, is_async: false, - run: (source, _language, goal) => impls.native!.parse(source, language, goal), + run: (source, _language, goal) => impls.native!.parse(source, language, goal) }); } @@ -270,8 +270,7 @@ export function get_benchmark_tasks( name: 'tsv-json-no-locations', tracking_key: `${group_name}/native-no-locations`, is_async: false, - run: (source, _language, goal) => - impls.native!.parse_no_locations!(source, language, goal), + run: (source, _language, goal) => impls.native!.parse_no_locations!(source, language, goal) }); } @@ -281,7 +280,7 @@ export function get_benchmark_tasks( name: 'tsv_wasm-json', tracking_key: `${group_name}/wasm`, is_async: false, - run: (source, _language, goal) => impls.wasm!.parse(source, language, goal), + run: (source, _language, goal) => impls.wasm!.parse(source, language, goal) }); } @@ -292,8 +291,7 @@ export function get_benchmark_tasks( name: 'tsv_wasm-json-no-locations', tracking_key: `${group_name}/wasm-no-locations`, is_async: false, - run: (source, _language, goal) => - impls.wasm!.parse_no_locations!(source, language, goal), + run: (source, _language, goal) => impls.wasm!.parse_no_locations!(source, language, goal) }); } @@ -303,8 +301,7 @@ export function get_benchmark_tasks( name: 'tsv-internal', tracking_key: `${group_name}/native-internal`, is_async: false, - run: (source, _language, goal) => - impls.native!.parse_internal!(source, language, goal), + run: (source, _language, goal) => impls.native!.parse_internal!(source, language, goal) }); } @@ -313,8 +310,7 @@ export function get_benchmark_tasks( name: 'tsv_wasm-internal', tracking_key: `${group_name}/wasm-internal`, is_async: false, - run: (source, _language, goal) => - impls.wasm!.parse_internal!(source, language, goal), + run: (source, _language, goal) => impls.wasm!.parse_internal!(source, language, goal) }); } @@ -328,7 +324,7 @@ export function get_benchmark_tasks( name: 'oxc-parser', tracking_key: `${group_name}/oxc`, is_async: false, - run: (source, _language, goal) => impls.oxc!.parse(source, language, goal), + run: (source, _language, goal) => impls.oxc!.parse(source, language, goal) }); } @@ -338,7 +334,7 @@ export function get_benchmark_tasks( name: 'oxc-parser-wasm', tracking_key: `${group_name}/oxc-wasm`, is_async: false, - run: (source, _language, goal) => impls.oxc_wasm!.parse(source, language, goal), + run: (source, _language, goal) => impls.oxc_wasm!.parse(source, language, goal) }); } } else { @@ -350,7 +346,7 @@ export function get_benchmark_tasks( run: () => { throw new Error('Use run_async for prettier'); }, - run_async: (source) => impls.canonical.format_async(source, language), + run_async: (source) => impls.canonical.format_async(source, language) }); // Native formatter @@ -359,7 +355,7 @@ export function get_benchmark_tasks( name: 'tsv', tracking_key: `${group_name}/native`, is_async: false, - run: (source) => impls.native!.format!(source, language), + run: (source) => impls.native!.format!(source, language) }); } @@ -376,8 +372,7 @@ export function get_benchmark_tasks( run: () => { throw new Error('Use run_async for tsv-forced-async'); }, - run_async: (source, language) => - Promise.resolve(impls.native!.format!(source, language)), + run_async: (source, language) => Promise.resolve(impls.native!.format!(source, language)) }); } @@ -387,7 +382,7 @@ export function get_benchmark_tasks( name: 'tsv_wasm', tracking_key: `${group_name}/wasm`, is_async: false, - run: (source) => impls.wasm!.format!(source, language), + run: (source) => impls.wasm!.format!(source, language) }); } @@ -400,7 +395,7 @@ export function get_benchmark_tasks( run: () => { throw new Error('Use run_async for oxfmt'); }, - run_async: (source) => impls.oxc!.format_async(source, language), + run_async: (source) => impls.oxc!.format_async(source, language) }); } @@ -410,7 +405,7 @@ export function get_benchmark_tasks( name: 'biome-wasm', tracking_key: `${group_name}/biome`, is_async: false, - run: (source) => impls.biome!.format(source, language), + run: (source) => impls.biome!.format(source, language) }); } @@ -420,7 +415,7 @@ export function get_benchmark_tasks( name: 'dprint-wasm', tracking_key: `${group_name}/dprint`, is_async: false, - run: (source) => impls.dprint!.format(source, language), + run: (source) => impls.dprint!.format(source, language) }); } } @@ -462,7 +457,7 @@ export function get_formatters(impls: InitializedImplementations): FormatterInfo name: 'prettier', is_async: true, format_async: (source, lang) => impls.canonical.format_async(source, lang), - supports_language: () => true, + supports_language: () => true }); // Native - sync @@ -471,7 +466,7 @@ export function get_formatters(impls: InitializedImplementations): FormatterInfo name: 'tsv', is_async: false, format: (source, lang) => impls.native!.format!(source, lang), - supports_language: () => true, + supports_language: () => true }); } @@ -481,7 +476,7 @@ export function get_formatters(impls: InitializedImplementations): FormatterInfo name: 'tsv_wasm', is_async: false, format: (source, lang) => impls.wasm!.format!(source, lang), - supports_language: () => true, + supports_language: () => true }); } @@ -491,7 +486,7 @@ export function get_formatters(impls: InitializedImplementations): FormatterInfo name: 'oxfmt', is_async: true, format_async: (source, lang) => impls.oxc!.format_async(source, lang), - supports_language: (lang) => impls.oxc!.supports_format_language(lang), + supports_language: (lang) => impls.oxc!.supports_format_language(lang) }); } @@ -501,7 +496,7 @@ export function get_formatters(impls: InitializedImplementations): FormatterInfo name: 'biome-wasm', is_async: false, format: (source, lang) => impls.biome!.format(source, lang), - supports_language: (lang) => impls.biome!.supports_format_language(lang), + supports_language: (lang) => impls.biome!.supports_format_language(lang) }); } @@ -511,7 +506,7 @@ export function get_formatters(impls: InitializedImplementations): FormatterInfo name: 'dprint-wasm', is_async: false, format: (source, lang) => impls.dprint!.format(source, lang), - supports_language: (lang) => impls.dprint!.supports_format_language(lang), + supports_language: (lang) => impls.dprint!.supports_format_language(lang) }); } @@ -537,6 +532,6 @@ export function get_alternative_versions(impls: InitializedImplementations): Alt biome: impls.biome?.versions.wasm, // The plugin version is the one worth citing — `@dprint/formatter` is just // the Wasm host, the TS/JS formatting behavior lives in the plugin. - dprint: impls.dprint?.versions.typescript, + dprint: impls.dprint?.versions.typescript }; } diff --git a/benches/js/lib/napi.ts b/benches/js/lib/napi.ts index 768b15dde..3799fe0de 100644 --- a/benches/js/lib/napi.ts +++ b/benches/js/lib/napi.ts @@ -72,9 +72,7 @@ export class NapiImplementation implements TsvImplementation { try { await stat(path); } catch { - throw new Error( - `N-API addon not found at ${path}. Run 'deno task build:napi' first.`, - ); + throw new Error(`N-API addon not found at ${path}. Run 'deno task build:napi' first.`); } // `process.dlopen` loads a native addon from any path/extension into the // passed module's `exports` — the supported way to load a `.so`/`.dylib` @@ -96,7 +94,7 @@ export class NapiImplementation implements TsvImplementation { return { svelte: this.addon.parse_svelte, typescript: this.addon.parse_typescript, - css: this.addon.parse_css, + css: this.addon.parse_css }; } @@ -104,7 +102,7 @@ export class NapiImplementation implements TsvImplementation { return { svelte: this.addon.parse_internal_svelte, typescript: this.addon.parse_internal_typescript, - css: this.addon.parse_internal_css, + css: this.addon.parse_internal_css }; } @@ -112,7 +110,7 @@ export class NapiImplementation implements TsvImplementation { return { svelte: this.addon.format_svelte, typescript: this.addon.format_typescript, - css: this.addon.format_css, + css: this.addon.format_css }; } @@ -120,7 +118,7 @@ export class NapiImplementation implements TsvImplementation { private get parse_no_locations_fns(): Partial string>> { return { svelte: this.addon.parse_svelte_no_locations, - typescript: this.addon.parse_typescript_no_locations, + typescript: this.addon.parse_typescript_no_locations }; } diff --git a/benches/js/lib/oxc.ts b/benches/js/lib/oxc.ts index c901bd2f4..9aaaba506 100644 --- a/benches/js/lib/oxc.ts +++ b/benches/js/lib/oxc.ts @@ -5,7 +5,12 @@ * oxfmt: Fast TypeScript/JS/CSS/Svelte formatter (Svelte is experimental as of 0.49) */ -import { type Language, LANGUAGE_EXTENSIONS, type ParseGoal, type TsvImplementation } from './types.ts'; +import { + type Language, + LANGUAGE_EXTENSIONS, + type ParseGoal, + type TsvImplementation +} from './types.ts'; import type { OxcVersions } from './versions.ts'; /** oxc-parser module types */ @@ -13,7 +18,7 @@ interface OxcParserModule { parseSync: ( filename: string, source: string, - options?: { sourceType?: 'script' | 'module' }, + options?: { sourceType?: 'script' | 'module' } ) => { program: unknown; errors: unknown[] }; } @@ -31,7 +36,7 @@ interface OxfmtModule { format: ( filename: string, source: string, - options?: OxfmtFormatOptions, + options?: OxfmtFormatOptions ) => Promise<{ code: string; errors: unknown[] }>; } @@ -83,7 +88,7 @@ export class OxcImplementation implements TsvImplementation { // A test262 goal pins oxc's `sourceType` so it's scored at the declared // goal like tsv/acorn, instead of oxc's filename-based inference. - const options = goal ? {sourceType: goal} : undefined; + const options = goal ? { sourceType: goal } : undefined; const result = this._parser.parseSync(`file${LANGUAGE_EXTENSIONS[language]}`, source, options); // Read `errors` once into a local: the WASI sibling's getter is consume-once @@ -124,7 +129,7 @@ export class OxcImplementation implements TsvImplementation { useTabs: true, printWidth: 100, singleQuote: true, - trailingComma: 'none', + trailingComma: 'none' }; // oxfmt gates .svelte handling behind the `svelte` config key (experimental as of 0.49). if (language === 'svelte') options.svelte = {}; @@ -132,7 +137,7 @@ export class OxcImplementation implements TsvImplementation { const result = await this._formatter.format( `file${LANGUAGE_EXTENSIONS[language]}`, source, - options, + options ); const errors = result.errors; diff --git a/benches/js/lib/oxc_wasm.ts b/benches/js/lib/oxc_wasm.ts index da603d536..581bdcdd3 100644 --- a/benches/js/lib/oxc_wasm.ts +++ b/benches/js/lib/oxc_wasm.ts @@ -8,7 +8,12 @@ * Supports: Parse only (TypeScript, JS). No formatting (oxfmt has no WASM variant). */ -import { type Language, LANGUAGE_EXTENSIONS, type ParseGoal, type TsvImplementation } from './types.ts'; +import { + type Language, + LANGUAGE_EXTENSIONS, + type ParseGoal, + type TsvImplementation +} from './types.ts'; import type { OxcVersions } from './versions.ts'; import { current_runtime } from './runtime.ts'; @@ -17,7 +22,7 @@ interface OxcParserWasmModule { parseSync: ( filename: string, source: string, - options?: { sourceType?: 'script' | 'module' }, + options?: { sourceType?: 'script' | 'module' } ) => { program: unknown; errors: unknown[] }; } @@ -43,9 +48,10 @@ export class OxcWasmImplementation implements TsvImplementation { // entry (`parser.wasi-browser.js`, `@napi-rs/wasm-runtime` + `WebAssembly`) // that Deno can load but Node can't. Pick per runtime so the // oxc-parser-wasm comparison row is available on all three. - const entry = current_runtime() === 'deno' - ? '@oxc-parser/binding-wasm32-wasi/parser.wasi-browser.js' - : '@oxc-parser/binding-wasm32-wasi'; + const entry = + current_runtime() === 'deno' + ? '@oxc-parser/binding-wasm32-wasi/parser.wasi-browser.js' + : '@oxc-parser/binding-wasm32-wasi'; const mod = await import(entry); this._parser = mod as OxcParserWasmModule; } @@ -72,7 +78,7 @@ export class OxcWasmImplementation implements TsvImplementation { throw new Error(`OXC WASM parser does not support ${language}`); } - const options = goal ? {sourceType: goal} : undefined; + const options = goal ? { sourceType: goal } : undefined; const result = this._parser.parseSync(`file${LANGUAGE_EXTENSIONS[language]}`, source, options); // Read `errors` exactly ONCE: on the WASI binding it's a CONSUME-ONCE getter — diff --git a/benches/js/lib/parse_sanctions.ts b/benches/js/lib/parse_sanctions.ts index d8f7c71fb..c2ccbd05f 100644 --- a/benches/js/lib/parse_sanctions.ts +++ b/benches/js/lib/parse_sanctions.ts @@ -60,8 +60,9 @@ export const TS_FIXTURE_SANCTIONS: Sanction[] = [ // not a gap. { pattern: 'assert_import_assert/', - reason: "deprecated import assertions (`assert {…}`) — tsv supports the successor `with {…}` only", - }, + reason: + 'deprecated import assertions (`assert {…}`) — tsv supports the successor `with {…}` only' + } ]; /** First matching sanction reason for `path`, or null. */ diff --git a/benches/js/lib/perf_omit.ts b/benches/js/lib/perf_omit.ts index e7679bcef..0f940fcba 100644 --- a/benches/js/lib/perf_omit.ts +++ b/benches/js/lib/perf_omit.ts @@ -57,37 +57,38 @@ export const PERF_OMITS: PerfOmit[] = [ { task: 'typescript/canonical', path: 'kit/packages/kit/src/runtime/app/env', - reason: 'acorn-typescript cannot parse ambient const declarations (no .d.ts mode)', + reason: 'acorn-typescript cannot parse ambient const declarations (no .d.ts mode)' }, { task: 'typescript/oxc', path: 'kit/packages/kit/src/runtime/app/env', reason: - 'oxc (native + wasm) rejects ambient consts under the synthetic file.ts name (no path threading in the bench)', + 'oxc (native + wasm) rejects ambient consts under the synthetic file.ts name (no path threading in the bench)' }, { task: 'typescript/oxfmt', path: 'kit/packages/kit/src/runtime/app/env', reason: - 'oxfmt rejects ambient consts under the synthetic file.ts name (no path threading in the bench)', + 'oxfmt rejects ambient consts under the synthetic file.ts name (no path threading in the bench)' }, // acorn-typescript enforces the `arguments`-in-class-field-initializer early // error; tsv (permissive / defer-diagnostics policy) and prettier accept it. { task: 'typescript/canonical', path: 'svelte/packages/svelte/src/ambient.d.ts', - reason: 'acorn-typescript enforces an early error tsv defers (arguments in class field init)', - }, + reason: 'acorn-typescript enforces an early error tsv defers (arguments in class field init)' + } ]; /** First matching omit reason for `(tracking_key, path)`, or `null` when the failure is unlisted. */ export function perf_omit_reason( omits: PerfOmit[], tracking_key: string, - path: string, + path: string ): string | null { return ( - omits.find((o) => (o.task === undefined || tracking_key.includes(o.task)) && path.includes(o.path)) - ?.reason ?? null + omits.find( + (o) => (o.task === undefined || tracking_key.includes(o.task)) && path.includes(o.path) + )?.reason ?? null ); } diff --git a/benches/js/lib/prettier_cache.ts b/benches/js/lib/prettier_cache.ts index 76c86651a..1d4bddef8 100644 --- a/benches/js/lib/prettier_cache.ts +++ b/benches/js/lib/prettier_cache.ts @@ -65,8 +65,8 @@ export class PrettierCache { schema: CACHE_SCHEMA, versions, options: prettier_options_json, - prettier_debug: process.env.PRETTIER_DEBUG ?? null, - }), + prettier_debug: process.env.PRETTIER_DEBUG ?? null + }) ) .digest('hex'); } diff --git a/benches/js/lib/report.ts b/benches/js/lib/report.ts index a9ebb42fb..ea26ea106 100644 --- a/benches/js/lib/report.ts +++ b/benches/js/lib/report.ts @@ -31,10 +31,10 @@ function coverage_pct(processed: number, total: number): number { * (`name processed/total (pct%)`) lives in exactly one place. */ function format_coverage_line( - rows: ReadonlyArray<{ name: string; processed: number; total: number }>, + rows: ReadonlyArray<{ name: string; processed: number; total: number }> ): string { const parts = rows.map( - (e) => `${e.name} ${e.processed}/${e.total} (${coverage_pct(e.processed, e.total)}%)`, + (e) => `${e.name} ${e.processed}/${e.total} (${coverage_pct(e.processed, e.total)}%)` ); return `**Coverage:** ${parts.join(', ')}`; } @@ -49,7 +49,7 @@ function create_bar(value: number, max: number, width = 40): string { const CANONICAL_PARSERS: Record = { svelte: 'svelte/compiler', typescript: 'acorn-typescript', - css: 'svelte/compiler', + css: 'svelte/compiler' }; /** Known canonical formatter name */ @@ -82,7 +82,7 @@ const DISPLAY_ORDER = [ 'dprint-wasm', 'oxc-parser', 'oxc-parser-wasm', - 'oxfmt', + 'oxfmt' ]; /** Sort results by stable display order */ @@ -100,7 +100,7 @@ function sort_by_display_order(results: BenchmarkResult[]): BenchmarkResult[] { /** Generate the summary report */ export function generate_summary_report( all_group_results: GroupResults[], - languages: Language[], + languages: Language[] ): string { const lines: string[] = []; @@ -167,23 +167,24 @@ export function generate_summary_report( // Show canonical first (baseline) if (canonical_result) { lines.push( - ` ${canonical_result.name.padEnd(max_name_len)} ${ - create_bar(canonical_result.stats.mean_ns, max_time) - } ${fmt(canonical_result.stats.mean_ns)}`, + ` ${canonical_result.name.padEnd(max_name_len)} ${create_bar( + canonical_result.stats.mean_ns, + max_time + )} ${fmt(canonical_result.stats.mean_ns)}` ); } // Show alternatives in stable display order (tsv variants, then third-party) const alternatives = sort_by_display_order( - main_results.filter((r) => r.name !== canonical_name), + main_results.filter((r) => r.name !== canonical_name) ); for (const result of alternatives) { const comparison = format_comparison(baseline, result.stats.mean_ns); lines.push( - ` ${result.name.padEnd(max_name_len)} ${create_bar(result.stats.mean_ns, max_time)} ${ - fmt(result.stats.mean_ns) - } ${comparison}`, + ` ${result.name.padEnd(max_name_len)} ${create_bar(result.stats.mean_ns, max_time)} ${fmt( + result.stats.mean_ns + )} ${comparison}` ); } @@ -191,19 +192,18 @@ export function generate_summary_report( // apples-to-apples comparison with oxc's span-only default AST (plain // `tsv-json` carries the richer loc-bearing drop-in AST oxc omits). Emitted // only where both rows exist (TS/JS — oxc doesn't parse svelte/css). - for ( - const [noloc, oxc] of [ - ['tsv-json-no-locations', 'oxc-parser'], - ['tsv_wasm-json-no-locations', 'oxc-parser-wasm'], - ] as const - ) { + for (const [noloc, oxc] of [ + ['tsv-json-no-locations', 'oxc-parser'], + ['tsv_wasm-json-no-locations', 'oxc-parser-wasm'] + ] as const) { const noloc_result = results.find((r) => r.name === noloc); const oxc_result = results.find((r) => r.name === oxc); if (noloc_result && oxc_result) { lines.push( - ` ↳ ${noloc} vs ${oxc}: ${ - format_comparison(oxc_result.stats.mean_ns, noloc_result.stats.mean_ns) - } (payload-matched, span-only)`, + ` ↳ ${noloc} vs ${oxc}: ${format_comparison( + oxc_result.stats.mean_ns, + noloc_result.stats.mean_ns + )} (payload-matched, span-only)` ); } } @@ -217,9 +217,10 @@ export function generate_summary_report( if (json_result) { const json_overhead = json_result.stats.mean_ns / internal_result.stats.mean_ns; lines.push( - ` ${internal_result.name.padEnd(max_name_len)} ${ - create_bar(internal_result.stats.mean_ns, max_time) - } ${fmt(internal_result.stats.mean_ns)} (${json_overhead.toFixed(1)}x JSON overhead)`, + ` ${internal_result.name.padEnd(max_name_len)} ${create_bar( + internal_result.stats.mean_ns, + max_time + )} ${fmt(internal_result.stats.mean_ns)} (${json_overhead.toFixed(1)}x JSON overhead)` ); } } @@ -248,22 +249,23 @@ export function generate_summary_report( // Show canonical first (baseline) lines.push( - ` ${canonical_result.name.padEnd(max_name_len)} ${ - create_bar(canonical_result.stats.mean_ns, max_time) - } ${fmt(canonical_result.stats.mean_ns)}`, + ` ${canonical_result.name.padEnd(max_name_len)} ${create_bar( + canonical_result.stats.mean_ns, + max_time + )} ${fmt(canonical_result.stats.mean_ns)}` ); // Show alternatives in stable display order (tsv variants, then third-party) const alternatives = sort_by_display_order( - results.filter((r) => r.name !== CANONICAL_FORMATTER), + results.filter((r) => r.name !== CANONICAL_FORMATTER) ); for (const result of alternatives) { const comparison = format_comparison(baseline, result.stats.mean_ns); lines.push( - ` ${result.name.padEnd(max_name_len)} ${create_bar(result.stats.mean_ns, max_time)} ${ - fmt(result.stats.mean_ns) - } ${comparison}`, + ` ${result.name.padEnd(max_name_len)} ${create_bar(result.stats.mean_ns, max_time)} ${fmt( + result.stats.mean_ns + )} ${comparison}` ); } } @@ -281,7 +283,7 @@ export function generate_skipped_files_report( skipped_files: Map>, max_error_length = 200, verbose = false, - task_tracking_by_group?: Map>, + task_tracking_by_group?: Map> ): string | null { if (skipped_files.size === 0) return null; @@ -371,9 +373,8 @@ export function generate_skipped_files_report( const failed_in = is_universal_tsv_failure(lang, benchmarks) ? 'all tsv variants' : benchmarks.map((b) => tracking_key_display(b, task_tracking_by_group)).join(', '); - const prefix = benchmarks.length === 1 - ? 'Failed in' - : `Failed in ${benchmarks.length} benchmarks`; + const prefix = + benchmarks.length === 1 ? 'Failed in' : `Failed in ${benchmarks.length} benchmarks`; lines.push(` ${prefix}: ${failed_in}`); lines.push(''); } @@ -405,7 +406,7 @@ export function generate_versions_info(versions: { lines.push('-'.repeat(80)); lines.push('Versions:'); lines.push( - ` svelte@${versions.svelte}, acorn@${versions.acorn}, @sveltejs/acorn-typescript@${versions.acorn_ts}`, + ` svelte@${versions.svelte}, acorn@${versions.acorn}, @sveltejs/acorn-typescript@${versions.acorn_ts}` ); lines.push(` prettier@${versions.prettier}, prettier-plugin-svelte@${versions.prettier_svelte}`); @@ -443,7 +444,7 @@ function lookup_iterated( group_name: string, display_name: string, iterated_counts: Map | undefined, - task_tracking_by_group: Map> | undefined, + task_tracking_by_group: Map> | undefined ): number | undefined { if (!iterated_counts || !task_tracking_by_group) return undefined; const tracking_key = task_tracking_by_group.get(group_name)?.get(display_name); @@ -468,7 +469,7 @@ function format_ratio(r: number): string { */ export function generate_group_bench_table_markdown( results: BenchmarkResult[], - baseline: string | undefined, + baseline: string | undefined ): string { if (results.length === 0) return '(no results)'; @@ -510,7 +511,7 @@ export function generate_group_bench_table_markdown( `p99 (${unit_str})`, `min (${unit_str})`, `max (${unit_str})`, - vs_header, + vs_header ]); for (let row_index = 0; row_index < results.length; row_index++) { @@ -534,7 +535,7 @@ export function generate_group_bench_table_markdown( tail_cell(r.stats.p99_ns), fmt(r.stats.min_ns), fmt(r.stats.max_ns), - vs_cell, + vs_cell ]); } @@ -563,7 +564,7 @@ function build_comparison_data( all_group_results: GroupResults[], languages: Language[], iterated_counts: Map | undefined, - task_tracking_by_group: Map> | undefined, + task_tracking_by_group: Map> | undefined ): ComparisonSection[] { function get_mean_ns(group_name: string, task_name: string): number | null { const group = all_group_results.find((g) => g.name === group_name); @@ -588,7 +589,7 @@ function build_comparison_data( if (tsv_ns === null || prettier_ns === null) continue; const comparisons: ComparisonRow['comparisons'] = [ - { name: 'prettier', ratio: ratio(tsv_ns, prettier_ns) }, + { name: 'prettier', ratio: ratio(tsv_ns, prettier_ns) } ]; const oxfmt_ns = get_mean_ns(group_name, 'oxfmt'); if (oxfmt_ns !== null) comparisons.push({ name: 'oxfmt', ratio: ratio(tsv_ns, oxfmt_ns) }); @@ -597,7 +598,7 @@ function build_comparison_data( operation: 'format', language: lang, files: lookup_iterated(group_name, 'tsv', iterated_counts, task_tracking_by_group), - comparisons, + comparisons }); } @@ -609,7 +610,7 @@ function build_comparison_data( if (tsv_ns === null || canonical_ns === null) continue; const comparisons: ComparisonRow['comparisons'] = [ - { name: 'svelte', ratio: ratio(tsv_ns, canonical_ns) }, + { name: 'svelte', ratio: ratio(tsv_ns, canonical_ns) } ]; const oxc_ns = get_mean_ns(group_name, 'oxc-parser'); if (oxc_ns !== null) comparisons.push({ name: 'oxc-parser', ratio: ratio(tsv_ns, oxc_ns) }); @@ -618,7 +619,7 @@ function build_comparison_data( operation: 'parse', language: lang, files: lookup_iterated(group_name, 'tsv-json', iterated_counts, task_tracking_by_group), - comparisons, + comparisons }); } @@ -636,7 +637,7 @@ function build_comparison_data( if (tsv_wasm_ns === null || prettier_ns === null) continue; const comparisons: ComparisonRow['comparisons'] = [ - { name: 'prettier', ratio: ratio(tsv_wasm_ns, prettier_ns) }, + { name: 'prettier', ratio: ratio(tsv_wasm_ns, prettier_ns) } ]; const biome_ns = get_mean_ns(group_name, 'biome-wasm'); if (biome_ns !== null) { @@ -653,7 +654,7 @@ function build_comparison_data( operation: 'format', language: lang, files: lookup_iterated(group_name, 'tsv_wasm', iterated_counts, task_tracking_by_group), - comparisons, + comparisons }); } @@ -665,7 +666,7 @@ function build_comparison_data( if (tsv_wasm_ns === null || canonical_ns === null) continue; const comparisons: ComparisonRow['comparisons'] = [ - { name: 'svelte', ratio: ratio(tsv_wasm_ns, canonical_ns) }, + { name: 'svelte', ratio: ratio(tsv_wasm_ns, canonical_ns) } ]; const oxc_wasm_ns = get_mean_ns(group_name, 'oxc-parser-wasm'); if (oxc_wasm_ns !== null) { @@ -676,7 +677,7 @@ function build_comparison_data( operation: 'parse', language: lang, files: lookup_iterated(group_name, 'tsv_wasm-json', iterated_counts, task_tracking_by_group), - comparisons, + comparisons }); } @@ -699,13 +700,13 @@ export function generate_comparison_summary( all_group_results: GroupResults[], languages: Language[], iterated_counts?: Map, - task_tracking_by_group?: Map>, + task_tracking_by_group?: Map> ): string { const sections = build_comparison_data( all_group_results, languages, iterated_counts, - task_tracking_by_group, + task_tracking_by_group ); const lines: string[] = []; @@ -736,39 +737,30 @@ export function generate_comparison_summary( } // Fairness notes (only shown when oxc-parser data is present) - const has_native_oxc = sections.some((s) => - s.label === 'tsv' && - s.rows.some((r) => r.comparisons.some((c) => c.name === 'oxc-parser')) + const has_native_oxc = sections.some( + (s) => + s.label === 'tsv' && s.rows.some((r) => r.comparisons.some((c) => c.name === 'oxc-parser')) ); - const has_wasm_oxc = sections.some((s) => - s.label === 'tsv_wasm' && - s.rows.some((r) => r.comparisons.some((c) => c.name === 'oxc-parser-wasm')) + const has_wasm_oxc = sections.some( + (s) => + s.label === 'tsv_wasm' && + s.rows.some((r) => r.comparisons.some((c) => c.name === 'oxc-parser-wasm')) ); lines.push(''); lines.push(' (`Nx` = self is N× faster; `(Mf)` = files the timing reflects)'); lines.push(' (parse canonical: svelte/compiler for .svelte/.css, acorn-typescript for .ts)'); if (has_native_oxc || has_wasm_oxc) { + lines.push(' (oxc-parser — native and wasm — serializes the AST to JSON in Rust and'); lines.push( - ' (oxc-parser — native and wasm — serializes the AST to JSON in Rust and', - ); - lines.push( - ' deserializes in JS, the same eager materialization as tsv-json — apples-to-apples)', - ); - lines.push( - ' (tsv-internal/tsv_wasm-internal are parse-only, no JS materialization;', - ); - lines.push( - ' oxc has no comparably cheap mode, so they have no oxc counterpart)', + ' deserializes in JS, the same eager materialization as tsv-json — apples-to-apples)' ); + lines.push(' (tsv-internal/tsv_wasm-internal are parse-only, no JS materialization;'); + lines.push(' oxc has no comparably cheap mode, so they have no oxc counterpart)'); } lines.push(' (format groups include parse time — each formatter parses internally)'); - lines.push( - ' (oxfmt formats JS/TS natively; its css/svelte rows route through its BUNDLED', - ); - lines.push( - ' prettier — native-vs-native reads apply to the typescript group only)', - ); + lines.push(' (oxfmt formats JS/TS natively; its css/svelte rows route through its BUNDLED'); + lines.push(' prettier — native-vs-native reads apply to the typescript group only)'); return lines.join('\n'); } @@ -783,13 +775,13 @@ export function generate_comparison_markdown( all_group_results: GroupResults[], languages: Language[], iterated_counts?: Map, - task_tracking_by_group?: Map>, + task_tracking_by_group?: Map> ): string | null { const sections = build_comparison_data( all_group_results, languages, iterated_counts, - task_tracking_by_group, + task_tracking_by_group ); if (sections.length === 0) return null; @@ -813,31 +805,32 @@ export function generate_comparison_markdown( } // Fairness notes (only shown when oxc-parser data is present) - const has_native_oxc = sections.some((s) => - s.label === 'tsv' && - s.rows.some((r) => r.comparisons.some((c) => c.name === 'oxc-parser')) + const has_native_oxc = sections.some( + (s) => + s.label === 'tsv' && s.rows.some((r) => r.comparisons.some((c) => c.name === 'oxc-parser')) ); - const has_wasm_oxc = sections.some((s) => - s.label === 'tsv_wasm' && - s.rows.some((r) => r.comparisons.some((c) => c.name === 'oxc-parser-wasm')) + const has_wasm_oxc = sections.some( + (s) => + s.label === 'tsv_wasm' && + s.rows.some((r) => r.comparisons.some((c) => c.name === 'oxc-parser-wasm')) ); const notes: string[] = [ '`Nx` is speedup — self is N× faster than the named opponent', "`(Mf)` is the self impl's iterated count (per-group intersection in default mode; per-impl success set in `BENCH_MODE=union`)", - 'Parse canonical: svelte/compiler for .svelte/.css, acorn-typescript for .ts', + 'Parse canonical: svelte/compiler for .svelte/.css, acorn-typescript for .ts' ]; if (has_native_oxc || has_wasm_oxc) { notes.push( - 'oxc-parser (native and wasm) serializes the AST to JSON in Rust and deserializes it in JS — the same eager materialization as tsv-json/tsv_wasm-json, so these parse rows are apples-to-apples', + 'oxc-parser (native and wasm) serializes the AST to JSON in Rust and deserializes it in JS — the same eager materialization as tsv-json/tsv_wasm-json, so these parse rows are apples-to-apples' ); notes.push( - 'tsv-internal/tsv_wasm-internal are parse-only (no JS materialization) and have no oxc counterpart — oxc exposes no comparably cheap mode (its JS API always serializes; experimentalLazy is setup-dominated)', + 'tsv-internal/tsv_wasm-internal are parse-only (no JS materialization) and have no oxc counterpart — oxc exposes no comparably cheap mode (its JS API always serializes; experimentalLazy is setup-dominated)' ); } notes.push('Format groups include parse time — each formatter parses internally'); notes.push( - 'oxfmt formats JS/TS natively; its css/svelte rows route through its bundled prettier (+ svelte plugin, with the embedded `\n
{x.a}
\n', typescript: 'import {x} from "y";const o={a:1,b:2};function f(n:T):T{return n}/* c */type U={a:number;b:string};async function g(){await Promise.resolve(1)}', - css: - '/* c */@media (min-width:1px){.foo>.bar:hover{color:red;display:flex;gap:1px}}.baz,.qux{margin:0}', + css: '/* c */@media (min-width:1px){.foo>.bar:hover{color:red;display:flex;gap:1px}}.baz,.qux{margin:0}' }; interface Failure { @@ -66,27 +65,28 @@ function record_fail(f: Failure): void { // the FFI library + the `deno`-target WASM bundle; Node/Bun run the N-API addon + // the `nodejs` target (`wasm_artifact_path` resolves the runtime's own bundle). const wasm_target = current_runtime() === 'deno' ? 'deno' : 'nodejs'; -const native_check = current_runtime() === 'deno' - ? { - label: `FFI (${env.TSV_FFI_PROFILE ?? 'release'})`, - path: get_library_path(), - binding_crates: ['tsv_ffi'], - rebuild: 'deno task build:ffi', - } - : { - label: 'N-API', - path: get_napi_library_path(), - binding_crates: ['tsv_napi'], - rebuild: 'deno task build:napi', - }; +const native_check = + current_runtime() === 'deno' + ? { + label: `FFI (${env.TSV_FFI_PROFILE ?? 'release'})`, + path: get_library_path(), + binding_crates: ['tsv_ffi'], + rebuild: 'deno task build:ffi' + } + : { + label: 'N-API', + path: get_napi_library_path(), + binding_crates: ['tsv_napi'], + rebuild: 'deno task build:napi' + }; await check_artifact_freshness([ native_check, { label: `WASM (all/${wasm_target})`, path: wasm_artifact_path('all'), binding_crates: WASM_CRATES, - rebuild: `deno task build:wasm:all:${wasm_target}`, - }, + rebuild: `deno task build:wasm:all:${wasm_target}` + } ]); // Friendly preflight: the canonical impls (prettier + svelte/compiler) resolve @@ -142,7 +142,7 @@ for (const lang of LANGUAGES) { kind: 'format', lang, impl: fmt.name, - reason: `second pass threw: ${msg}`, + reason: `second pass threw: ${msg}` }); continue; } diff --git a/crates/tsv_debug/src/cli/commands/gap_audit_known.txt b/crates/tsv_debug/src/cli/commands/gap_audit_known.txt index 194df1506..a33aed4cf 100644 --- a/crates/tsv_debug/src/cli/commands/gap_audit_known.txt +++ b/crates/tsv_debug/src/cli/commands/gap_audit_known.txt @@ -12,7 +12,7 @@ # the gate rather than being pinned. # # Format: KINDSHAPEPAYLOADS -# shapes: 540 +# shapes: 530 DROPPED !)⟨⟩!. annotation,block,jsdoc_cast DROPPED #⟨⟩IDENT annotation,block,jsdoc_cast,line,multiline DROPPED #⟨⟩\ annotation,block,jsdoc_cast,line,multiline @@ -61,7 +61,6 @@ DROPPED ()⟨⟩) annotation,block,jsdoc_cast,line,multiline DROPPED ()⟨⟩)); annotation,block,jsdoc_cast,line,multiline DROPPED ()⟨⟩); annotation,block,jsdoc_cast,line,multiline DROPPED ()⟨⟩␣ annotation,block,jsdoc_cast,line,multiline -DROPPED (/)⟨⟩␣ annotation,block,jsdoc_cast DROPPED (<⟨⟩IDENT annotation,block,jsdoc_cast,multiline DROPPED ([(⟨⟩IDENT line DROPPED (⟨⟩' line @@ -129,8 +128,6 @@ DROPPED */⟨⟩, annotation,block,jsdoc_cast,line,multiline DROPPED */⟨⟩,/* line DROPPED */⟨⟩/* line,multiline DROPPED ++⟨⟩); annotation,block,jsdoc_cast,multiline -DROPPED +⟨⟩IDENT line -DROPPED +⟨⟩␣ annotation,block,jsdoc_cast,line,multiline DROPPED ,(⟨⟩IDENT annotation,block,jsdoc_cast,line,multiline DROPPED ,,,⟨⟩,/* annotation,block,jsdoc_cast DROPPED ,,⟨⟩, annotation,block,jsdoc_cast @@ -225,13 +222,11 @@ DROPPED IDENT⟨⟩): annotation,block,jsdoc_cast,line,multiline DROPPED IDENT⟨⟩); annotation,block,jsdoc_cast,line,multiline DROPPED IDENT⟨⟩)=> annotation,block,jsdoc_cast,line,multiline DROPPED IDENT⟨⟩)} annotation,block,jsdoc_cast,line,multiline -DROPPED IDENT⟨⟩+ annotation,block,jsdoc_cast,line,multiline DROPPED IDENT⟨⟩, annotation,block,jsdoc_cast,line,multiline DROPPED IDENT⟨⟩,/* line,multiline DROPPED IDENT⟨⟩,// line DROPPED IDENT⟨⟩,>( annotation,block,jsdoc_cast,multiline DROPPED IDENT⟨⟩,>/ annotation,block,jsdoc_cast,multiline -DROPPED IDENT⟨⟩/ annotation,block,jsdoc_cast,line,multiline DROPPED IDENT⟨⟩/* line,multiline DROPPED IDENT⟨⟩: annotation,block,jsdoc_cast,line,multiline DROPPED IDENT⟨⟩:[. annotation,block,jsdoc_cast,line,multiline @@ -310,7 +305,6 @@ DROPPED [⟨⟩]{} annotation,block,jsdoc_cast,line,multiline DROPPED [⟨⟩]| annotation,block,jsdoc_cast,line,multiline DROPPED [⟨⟩{ annotation,block,jsdoc_cast,multiline DROPPED [⟨⟩␣ annotation,block,jsdoc_cast,line,multiline -DROPPED \(/⟨⟩) annotation,block,jsdoc_cast,line,multiline DROPPED ]))⟨⟩[]; annotation,block,jsdoc_cast DROPPED ])[⟨⟩])) annotation,block,jsdoc_cast,line,multiline DROPPED ])[⟨⟩]); annotation,block,jsdoc_cast,line,multiline @@ -424,13 +418,11 @@ DROPPED ␣⟨⟩);/ annotation,block,jsdoc_cast,multiline DROPPED ␣⟨⟩)} annotation,block,jsdoc_cast,multiline DROPPED ␣⟨⟩)}' annotation,block,jsdoc_cast,multiline DROPPED ␣⟨⟩)}` annotation,block,jsdoc_cast,multiline -DROPPED ␣⟨⟩+ annotation,block,jsdoc_cast,line,multiline DROPPED ␣⟨⟩, annotation,block,jsdoc_cast,line,multiline DROPPED ␣⟨⟩,' annotation,block,jsdoc_cast DROPPED ␣⟨⟩,/* annotation,block,jsdoc_cast DROPPED ␣⟨⟩,]; annotation,block,jsdoc_cast DROPPED ␣⟨⟩... multiline -DROPPED ␣⟨⟩/ annotation,block,jsdoc_cast,line,multiline DROPPED ␣⟨⟩/* annotation,block,jsdoc_cast,line,multiline DROPPED ␣⟨⟩/** multiline DROPPED ␣⟨⟩// annotation,block,jsdoc_cast,line,multiline @@ -520,7 +512,6 @@ DOUBLE-PRINTED [⟨⟩IDENT annotation,block,jsdoc_cast,line,multiline DOUBLE-PRINTED [⟨⟩[ annotation,block,jsdoc_cast,line,multiline DOUBLE-PRINTED [⟨⟩{ annotation,block,jsdoc_cast,line,multiline DOUBLE-PRINTED [⟨⟩␣ annotation,block,jsdoc_cast,line,multiline -DOUBLE-PRINTED \)/⟨⟩␣ annotation,block,jsdoc_cast,line,multiline DOUBLE-PRINTED ]:⟨⟩␣ annotation,block,jsdoc_cast,line,multiline DOUBLE-PRINTED ]?:⟨⟩␣ annotation,block,jsdoc_cast,line,multiline DOUBLE-PRINTED ]]:⟨⟩␣ annotation,block,jsdoc_cast,line,multiline @@ -550,6 +541,5 @@ DOUBLE-PRINTED ␣⟨⟩protected line DOUBLE-PRINTED ␣⟨⟩public line DOUBLE-PRINTED ␣⟨⟩readonly line DOUBLE-PRINTED ␣⟨⟩{ line -DOUBLE-PRINTED ␣⟨⟩}) annotation,block,jsdoc_cast,line,multiline DOUBLE-PRINTED ␣⟨⟩}] annotation,block,jsdoc_cast,line,multiline DOUBLE-PRINTED ␣⟨⟩␣ annotation,block,jsdoc_cast,line,multiline diff --git a/crates/tsv_debug/src/deno/sidecar.ts b/crates/tsv_debug/src/deno/sidecar.ts index a51679505..be0a05d71 100644 --- a/crates/tsv_debug/src/deno/sidecar.ts +++ b/crates/tsv_debug/src/deno/sidecar.ts @@ -24,7 +24,7 @@ const VERSIONS = { 'prettier-plugin-svelte': '4.1.1', svelte: '5.56.4', acorn: '8.16.0', - '@sveltejs/acorn-typescript': '1.0.11', + '@sveltejs/acorn-typescript': '1.0.11' } as const; // TODO verify there's not a better solution to use deno.json here, see the above NOTE too @@ -34,7 +34,11 @@ import * as prettier from 'npm:prettier@3.9.6'; // deno-lint-ignore no-import-prefix import prettierPluginSvelte from 'npm:prettier-plugin-svelte@4.1.1'; // deno-lint-ignore no-import-prefix -import { compile as svelteCompile, parse as svelteParse, parseCss } from 'npm:svelte@5.56.4/compiler'; +import { + compile as svelteCompile, + parse as svelteParse, + parseCss +} from 'npm:svelte@5.56.4/compiler'; // deno-lint-ignore no-import-prefix import * as acorn from 'npm:acorn@8.16.0'; // deno-lint-ignore no-import-prefix @@ -155,9 +159,40 @@ function bakedSkeleton(code: string): string { // flow segment independently, so block-boundary whitespace vanishes while inline whitespace // (and text presence) is preserved. `
` runs are kept verbatim (Svelte preserves them).
 const BLOCK_TAGS = new Set([
-	'address', 'article', 'aside', 'blockquote', 'div', 'dl', 'dt', 'dd', 'fieldset', 'figure',
-	'figcaption', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hr', 'li',
-	'main', 'nav', 'ol', 'p', 'section', 'table', 'thead', 'tbody', 'tr', 'td', 'th', 'ul',
+	'address',
+	'article',
+	'aside',
+	'blockquote',
+	'div',
+	'dl',
+	'dt',
+	'dd',
+	'fieldset',
+	'figure',
+	'figcaption',
+	'footer',
+	'form',
+	'h1',
+	'h2',
+	'h3',
+	'h4',
+	'h5',
+	'h6',
+	'header',
+	'hr',
+	'li',
+	'main',
+	'nav',
+	'ol',
+	'p',
+	'section',
+	'table',
+	'thead',
+	'tbody',
+	'tr',
+	'td',
+	'th',
+	'ul'
 ]);
 const BR = '\x00'; // block-boundary sentinel (never appears in HTML)
 
@@ -177,7 +212,8 @@ function visibleSegments(body: string): string[] {
 			.replace(/]*>[\s\S]*?<\/style>/gi, '')
 			.replace(//g, '') // comments are not visible render
 			.replace(/<\/?([a-zA-Z][\w-]*)\b[^>]*>/g, (_f, tag) =>
-				BLOCK_TAGS.has(tag.toLowerCase()) ? BR : '');
+				BLOCK_TAGS.has(tag.toLowerCase()) ? BR : ''
+			);
 		for (const seg of marked.split(BR)) {
 			const text = seg.replace(/[ \t\r\n\f]+/g, ' ').trim();
 			if (text) segments.push(`text:${text}`);
@@ -199,7 +235,7 @@ function svelteRenderKey(source: string): string {
 	const compiled = svelteCompile(source, {
 		generate: 'server',
 		name: 'C',
-		filename: 'C.svelte',
+		filename: 'C.svelte'
 	}).js.code;
 	return JSON.stringify(visibleSegments(bakedSkeleton(compiled)));
 }
@@ -227,29 +263,29 @@ interface Response {
 async function dispatch(
 	tool: string,
 	content: string,
-	options?: Record,
+	options?: Record
 ): Promise {
 	switch (tool) {
 		case '__version_info': {
 			return {
 				runtime: Deno.version.deno,
 				typescript: Deno.version.typescript,
-				dependencies: VERSIONS,
+				dependencies: VERSIONS
 			};
 		}
 
 		case 'prettier': {
 			// Provide default filepath based on parser to help prettier make correct decisions
 			// (e.g., typescript parser without filepath hint might add unnecessary JSX disambiguation)
-			const filepath = options?.filepath ?? (
-				options?.parser === 'typescript'
+			const filepath =
+				options?.filepath ??
+				(options?.parser === 'typescript'
 					? 'file.ts'
 					: options?.parser === 'svelte'
-					? 'file.svelte'
-					: options?.parser === 'css'
-					? 'file.css'
-					: undefined
-			);
+						? 'file.svelte'
+						: options?.parser === 'css'
+							? 'file.css'
+							: undefined);
 			return await prettier.format(content, {
 				plugins: [prettierPluginSvelte],
 				useTabs: true,
@@ -257,7 +293,7 @@ async function dispatch(
 				singleQuote: true,
 				trailingComma: 'none',
 				parser: options?.parser as string | undefined,
-				filepath: filepath as string | undefined,
+				filepath: filepath as string | undefined
 			});
 		}
 
@@ -275,7 +311,7 @@ async function dispatch(
 			return ParserWithTS.parse(content, {
 				sourceType,
 				ecmaVersion: 2025,
-				locations: true,
+				locations: true
 			});
 		}
 
@@ -299,7 +335,7 @@ async function dispatch(
 				dev,
 				runes: true,
 				css: 'external',
-				cssHash: () => 'svelte-tsvhash',
+				cssHash: () => 'svelte-tsvhash'
 			});
 			return {
 				js: result.js.code,
@@ -310,8 +346,8 @@ async function dispatch(
 					code: w.code,
 					message: w.message,
 					start: w.start,
-					end: w.end,
-				})),
+					end: w.end
+				}))
 			};
 		}
 
@@ -352,7 +388,7 @@ for await (const line of lines) {
 			id: -1,
 			ok: false,
 			error: `Invalid JSON request: ${err instanceof Error ? err.message : String(err)}`,
-			duration_ms: Math.round(performance.now() - start),
+			duration_ms: Math.round(performance.now() - start)
 		};
 		console.log(JSON.stringify(response, jsonReplacer));
 		continue;
@@ -364,14 +400,14 @@ for await (const line of lines) {
 			id: req.id,
 			ok: true,
 			output,
-			duration_ms: Math.round(performance.now() - start),
+			duration_ms: Math.round(performance.now() - start)
 		};
 	} catch (err) {
 		response = {
 			id: req.id,
 			ok: false,
 			error: err instanceof Error ? err.message : String(err),
-			duration_ms: Math.round(performance.now() - start),
+			duration_ms: Math.round(performance.now() - start)
 		};
 	}
 
diff --git a/crates/tsv_lang/src/source_scan.rs b/crates/tsv_lang/src/source_scan.rs
index 677d26c68..3edcced8a 100644
--- a/crates/tsv_lang/src/source_scan.rs
+++ b/crates/tsv_lang/src/source_scan.rs
@@ -399,23 +399,84 @@ pub fn is_regex_start(bytes: &[u8], slash_pos: usize, lower_bound: usize) -> boo
         j -= 1;
         let b = bytes[j];
         if !b.is_ascii_whitespace() {
+            // An identifier byte usually ends an operand (`a / 2` divides), but a
+            // whole RESERVED word ending here is an operator, and a `/` after an
+            // operator opens a regex (`typeof /re/`, `void /re/`, `'a' in /re/`).
+            if is_identifier_byte(b) {
+                return word_before_regex(bytes, j + 1, lower_bound);
+            }
+            // A postfix `++`/`--` ends an operand, so the `/` after it DIVIDES
+            // (`aa++ / bb`). A lone `+`/`-` is a binary or unary operator, after
+            // which a regex may start (`aa + /re/.test(b)`), so the doubling is
+            // the whole discriminator.
+            if matches!(b, b'+' | b'-') && j > lower_bound && bytes[j - 1] == b {
+                return false;
+            }
             // Bytes that END an expression — a `/` after these is DIVISION. The
             // string/template closing quotes (`'` `"` `` ` ``) belong here: after a
             // literal like `'ab' / 2`, the `/` divides (skip_trivia already ate the
             // whole string, so this quote can only be its close).
-            return !(b.is_ascii_alphanumeric()
-                || b == b'_'
-                || b == b')'
-                || b == b']'
-                || b == b'\''
-                || b == b'"'
-                || b == b'`');
+            return !(b == b')' || b == b']' || b == b'\'' || b == b'"' || b == b'`');
         }
     }
     // Nothing significant before it (start of the scanned region) → regex.
     true
 }
 
+/// Whether the identifier ending at `word_end` (exclusive) is a reserved word an
+/// expression — and so a regex literal — may follow (`typeof /re/`, `void /re/`,
+/// `'a' in /re/`).
+///
+/// Only **reserved** words qualify: a reserved word can never be a variable, so
+/// reading one as an operator can never misclassify a real division. Contextual
+/// keywords are deliberately absent — `of` and `as` are legal identifiers, so
+/// `of / 2` must stay division. `await` is the one judgment call: reserved at
+/// `Goal::Module` (the default) and inside every async function, which is where
+/// `await /re/.test(x)` occurs; only at the rare `Goal::Script` is it an ordinary
+/// identifier whose `await / 2` would be misread.
+///
+/// A reserved word used as a PROPERTY NAME is an operand, not an operator
+/// (`a.in / bb` and `a.return / bb` both divide), so a `.` before the word
+/// disqualifies it.
+fn word_before_regex(bytes: &[u8], word_end: usize, lower_bound: usize) -> bool {
+    let mut start = word_end;
+    while start > lower_bound && is_identifier_byte(bytes[start - 1]) {
+        start -= 1;
+    }
+    // Matched as a pattern rather than scanned from a list so the compiler can
+    // switch on length first — this is the hot path for ordinary division, where
+    // the word is some identifier that matches nothing. A numeric literal
+    // (`1e3 / 2`) falls out here too, since no literal spells a keyword.
+    if !matches!(
+        &bytes[start..word_end],
+        b"return"
+            | b"typeof"
+            | b"instanceof"
+            | b"in"
+            | b"void"
+            | b"delete"
+            | b"case"
+            | b"do"
+            | b"else"
+            | b"throw"
+            | b"new"
+            | b"extends"
+            | b"yield"
+            | b"await"
+    ) {
+        return false;
+    }
+    // `.name` / `?.name` — a member access, so the word is an operand.
+    let mut j = start;
+    while j > lower_bound {
+        j -= 1;
+        if !bytes[j].is_ascii_whitespace() {
+            return bytes[j] != b'.';
+        }
+    }
+    true
+}
+
 /// Skip past a regex literal whose opening `/` is at `start`, returning the
 /// position just after the closing `/` and any trailing flags (bounded by
 /// `end`). Backslash-escape aware, and aware that a `/` inside a `[…]`
@@ -874,6 +935,60 @@ mod tests {
         assert!(is_regex_start(b"(/re/", 1, 1));
     }
 
+    #[test]
+    fn is_regex_start_reads_a_reserved_word_as_an_operator() {
+        // The prefix's length IS the `/` offset, so each case names its own
+        // position — no scan needed to locate it.
+        let at_slash = |prefix: &str, rest: &str| {
+            let src = format!("{prefix}{rest}");
+            is_regex_start(src.as_bytes(), prefix.len(), 0)
+        };
+
+        // A reserved word is an operator, so the `/` after it opens a regex.
+        for prefix in [
+            "typeof ",
+            "void ",
+            "'a' in ",
+            "return ",
+            "case ",
+            "throw ",
+            "yield ",
+            "await ",
+            "a instanceof ",
+        ] {
+            assert!(at_slash(prefix, "/re/"), "expected regex after `{prefix}`");
+        }
+
+        // An ordinary identifier is an operand — including one that merely ENDS
+        // with a keyword, one ending in `$` (an identifier byte the old exclusion
+        // list missed), and the contextual keywords that are legal variables.
+        for prefix in ["aa ", "notreturn ", "a$ ", "of ", "as "] {
+            assert!(
+                !at_slash(prefix, "/ bb"),
+                "expected division after `{prefix}`"
+            );
+        }
+
+        // A reserved word is a legal PROPERTY name, and a property is an operand.
+        for prefix in ["a.in ", "a.return ", "a?.typeof "] {
+            assert!(
+                !at_slash(prefix, "/ bb"),
+                "expected division after `{prefix}`"
+            );
+        }
+
+        // A postfix update ends an operand; a lone `+`/`-` does not.
+        for prefix in ["aa++ ", "aa-- ", "aa++"] {
+            assert!(
+                !at_slash(prefix, "/ bb"),
+                "expected division after `{prefix}`"
+            );
+        }
+        for prefix in ["aa + ", "aa - ", "aa = -", "aa = +"] {
+            assert!(at_slash(prefix, "/re/"), "expected regex after `{prefix}`");
+        }
+    }
+
     #[test]
     fn skip_regex_literal_handles_escapes_classes_and_flags() {
         // Plain literal: past the closing `/`.
diff --git a/crates/tsv_ts/src/parser/expression_lookahead.rs b/crates/tsv_ts/src/parser/expression_lookahead.rs
index 186c9c3b6..f1d13f1ee 100644
--- a/crates/tsv_ts/src/parser/expression_lookahead.rs
+++ b/crates/tsv_ts/src/parser/expression_lookahead.rs
@@ -8,7 +8,10 @@
 //
 // All functions operate on byte slices for performance (no tokenization needed).
 
-use super::scan::{is_identifier_start, is_word_at, skip_identifier, skip_whitespace_and_comments};
+use super::scan::{
+    is_identifier_start, is_word_at, skip_identifier, skip_numeric_literal,
+    skip_whitespace_and_comments,
+};
 use tsv_lang::source_scan::{TriviaProfile, is_regex_start, skip_regex_literal, skip_trivia};
 
 /// `<` at `pos` is `<=` comparison operator, not an angle bracket open
@@ -151,87 +154,229 @@ fn check_arrow_after_paren(bytes: &[u8], pos: usize) -> bool {
     false
 }
 
-/// Scan forward looking for `=>` (used after type annotations)
+/// Whether the return-type annotation opening at the `:` at `colon` is followed
+/// **immediately** by `=>` — the signal that the `(…)` before it was an arrow's
+/// parameter list, not a parenthesized expression.
 ///
-/// Properly handles:
-/// - Statement boundaries: stops at `;` (not an arrow function)
-/// - Nested structures: tracks depth for `()`, `[]`, `{}`, `<>` to find `=>` at depth 0
-/// - Type function signatures: `(x: (a: number) => void): T => ...` correctly finds outer `=>`
-fn scan_for_arrow(bytes: &[u8], mut pos: usize) -> bool {
-    let mut paren_depth = 0;
-    let mut bracket_depth = 0;
-    let mut brace_depth = 0;
-    let mut angle_depth = 0;
-
-    while pos < bytes.len() {
+/// Mirrors tsc's rule (`parseParenthesizedArrowFunctionExpression`): scan exactly
+/// ONE type, then require `=>` right after it. Hunting for any `=>` at bracket
+/// depth 0 is not enough, and tsc's own source names the counterexample —
+/// "`a ? (b): c` will have `(b):` parsed as a signature with a return type
+/// annotation". Two shapes put a stray `=>` within reach of such a scan, and in
+/// neither is the `:` a return-type colon:
+///
+/// - a depth-0 `?`, which in a type is only a conditional type's `?` and so
+///   requires an `extends` before it (`a ? (b, c) : d ? (p) => 1 : e`)
+/// - a `=>` belonging to a **function type** in the annotation, which is the
+///   type's own arrow and not the signature's (`a ? (b, c) : (p) => 1`)
+///
+/// Bytes that cannot occur at the top level of a type end the scan, so an
+/// assignment (`d = …`), a non-null assertion (`d! ? …`) or a regex body
+/// containing `=>` stops it rather than being scanned through.
+fn scan_for_arrow(bytes: &[u8], colon: usize) -> bool {
+    let end = bytes.len();
+    let mut pos = colon + 1;
+    // A `?` is type syntax only inside a conditional type, which is introduced by
+    // `extends`. Tracked as a flag rather than a count: an undercount would
+    // reject a valid annotation, and the whole point of this scan is to stop
+    // over-rejecting.
+    let mut saw_extends = false;
+    let mut position = TypePos::Full;
+
+    while pos < end {
         pos = skip_whitespace_and_comments(bytes, pos);
-        if pos >= bytes.len() {
+        if pos >= end {
             break;
         }
 
-        // Strings/templates are opaque (comments were already consumed above); a
-        // delimiter inside one isn't significant. No regex skip is needed here
-        // (unlike `scan_parens_then_arrow`): this walks type syntax after a `:` /
-        // `)`, where a `/…/` regex literal can't appear, so a stray `/` is just an
-        // insignificant byte.
-        if let Some(past) = skip_trivia(bytes, pos, bytes.len(), TriviaProfile::JS) {
+        // Strings and templates are opaque (comments were consumed above) and,
+        // as literal types, are atoms.
+        if let Some(past) = skip_trivia(bytes, pos, end, TriviaProfile::JS) {
+            position = TypePos::Atom;
             pos = past;
             continue;
         }
 
-        // Check if we're at the outermost nesting level (no open brackets/braces/parens/angles)
-        let at_depth_zero =
-            paren_depth == 0 && bracket_depth == 0 && brace_depth == 0 && angle_depth == 0;
+        // Consume whole identifiers in one step, so a name's bytes can never be
+        // read as operators and so a keyword is matched as a word (never as the
+        // opening of an `extendsFoo`).
+        if is_identifier_start(bytes[pos]) {
+            position = if is_word_at(bytes, pos, b"extends") {
+                // The extends-type and both branches are full types.
+                saw_extends = true;
+                TypePos::Full
+            } else if is_word_at(bytes, pos, b"new") || is_word_at(bytes, pos, b"abstract") {
+                // A construct type's `(params) =>` is the function-type shape.
+                TypePos::Full
+            } else if TYPE_PREFIX_WORDS.iter().any(|w| is_word_at(bytes, pos, w)) {
+                TypePos::Operand
+            } else {
+                TypePos::Atom
+            };
+            pos = skip_identifier(bytes, pos);
+            continue;
+        }
 
-        match bytes[pos] {
-            // Statement boundary - not an arrow function (only at depth 0)
-            // Semicolons inside braces are valid separators in object type literals
-            b';' if at_depth_zero => return false,
+        // A numeric literal type (`-1` included, via the `-` arm below).
+        if bytes[pos].is_ascii_digit() {
+            pos = skip_numeric_literal(bytes, pos);
+            position = TypePos::Atom;
+            continue;
+        }
 
-            // Track nesting depth
-            b'(' => paren_depth += 1,
-            b')' => {
-                if paren_depth > 0 {
-                    paren_depth -= 1;
+        match bytes[pos] {
+            b'(' => {
+                // A `(` after a complete atom is not type syntax — the type
+                // already ended, so the `:` was never a return-type colon
+                // (`d as (p) => T`, `async (p) => 1`).
+                if position == TypePos::Atom {
+                    return false;
                 }
-            }
-            b'[' => bracket_depth += 1,
-            b']' => {
-                if bracket_depth > 0 {
-                    bracket_depth -= 1;
+                let Some(close) = matching_delimiter_close(bytes, pos) else {
+                    return false;
+                };
+                // At a full-type position the `(` may open a function type's
+                // parameter list, whose `=>` belongs to the TYPE and must not be
+                // mistaken for the signature's. At an operand position the
+                // grammar has no function type, so it is a parenthesized type
+                // and a following `=>` is the enclosing arrow's.
+                if position == TypePos::Full && paren_starts_function_type(bytes, pos) {
+                    let after = skip_whitespace_and_comments(bytes, close + 1);
+                    if bytes.get(after) == Some(&b'=') && bytes.get(after + 1) == Some(&b'>') {
+                        // The function type's return type is itself a full type.
+                        pos = after + 2;
+                        position = TypePos::Full;
+                        continue;
+                    }
                 }
+                pos = close + 1;
+                position = TypePos::Atom;
             }
-            b'{' => brace_depth += 1,
-            b'}' => {
-                if brace_depth > 0 {
-                    brace_depth -= 1;
-                } else {
-                    // Unbalanced brace - end of scope
+            // An object or tuple/array type, or an index/array suffix on an atom.
+            b'{' | b'[' => {
+                let Some(close) = matching_delimiter_close(bytes, pos) else {
                     return false;
+                };
+                pos = close + 1;
+                position = TypePos::Atom;
+            }
+            b'<' => {
+                let Some(close) = matching_angle_close(bytes, pos + 1) else {
+                    return false;
+                };
+                // At a full-type position `<…>` opens a generic function type,
+                // whose parameter list follows; anywhere else it is a
+                // type-argument list, which completes an atom.
+                if position != TypePos::Full {
+                    position = TypePos::Atom;
                 }
+                pos = close + 1;
             }
-            b'<' if is_less_equal_op(bytes, pos) => pos += 1,
-            b'<' => angle_depth += 1,
-            b'>' if is_arrow_close(bytes, pos) => {} // `=>` handled by `=` match
-            b'>' if is_greater_equal_op(bytes, pos) => pos += 1,
-            b'>' if angle_depth > 0 => angle_depth -= 1,
-
-            // Check for `=>` at depth 0
-            b'=' if pos + 1 < bytes.len() && bytes[pos + 1] == b'>' && at_depth_zero => {
-                return true;
+            // The type ended here, so this `=>` is the signature's arrow.
+            b'=' if bytes.get(pos + 1) == Some(&b'>') => return true,
+            // Union / intersection constituents, qualified-name parts, and a
+            // negative literal type's sign all take a higher-precedence operand,
+            // never a bare function type.
+            b'|' | b'&' | b'.' | b'-' => {
+                position = TypePos::Operand;
+                pos += 1;
             }
-            b'=' if pos + 1 < bytes.len() && bytes[pos + 1] == b'>' => {
-                // Not at depth zero - skip past `=>` to avoid matching the `>` as angle close
+            // A conditional type's `?` and `:` each introduce a branch, which is
+            // a full type.
+            b'?' => {
+                if !saw_extends {
+                    return false;
+                }
+                position = TypePos::Full;
                 pos += 1;
             }
-
-            _ => {}
+            b':' => {
+                position = TypePos::Full;
+                pos += 1;
+            }
+            // Anything else cannot be type syntax, so the type ended without an
+            // arrow: a statement or list boundary (`;`, `,`), an operator
+            // (`d = …`, `d! ? …`, a `/…/` regex whose body holds a `=>`), or an
+            // unbalanced `)`/`]`/`}` closing the group this scan started inside.
+            _ => return false,
         }
-        pos += 1;
     }
     false
 }
 
+/// Where the type scan stands in the type grammar — which shapes the next token
+/// may legally take. The `Full`/`Operand` split is the byte-scan form of the
+/// type parser's own `fn_type_disallowed` flag; the two encode the same rule and
+/// must move together.
+#[derive(Clone, Copy, PartialEq, Eq)]
+enum TypePos {
+    /// A complete type may start here, function and construct types included.
+    Full,
+    /// A higher-precedence operand (a union/intersection constituent, a
+    /// `keyof`/`typeof` operand): a `(` is a parenthesized type, never a
+    /// function type's parameter list.
+    Operand,
+    /// A type atom just ended; only suffixes (`[]`, `<…>`, `.`) and operators
+    /// may follow.
+    Atom,
+}
+
+/// Type-level prefix keywords that take an operand of their own, so a `(` right
+/// after one still belongs to the type (`keyof (A | B)`, `typeof import('m')`)
+/// rather than ending it.
+const TYPE_PREFIX_WORDS: [&[u8]; 5] = [b"keyof", b"typeof", b"readonly", b"infer", b"import"];
+
+/// Whether the `(` at `paren` opens a **function type**'s parameter list rather
+/// than a parenthesized type — acorn-typescript's
+/// `tsIsUnambiguouslyStartOfFunctionType`, which decides whether a following
+/// `=>` belongs to the type (`(b: B) => C`) or terminates it (`(B | C) => …`,
+/// where the `=>` is the enclosing arrow's).
+///
+/// True for `()`, `(...`, and a parameter start (identifier, `this`, or a
+/// balanced `{…}`/`[…]` pattern) followed by `:`, `,`, `?`, `=`, or by `) =>`.
+///
+/// The token-level twin of this question, asked once the parser has committed to
+/// parsing a type, is `Parser::paren_starts_function_type_params` — same acorn
+/// rule, same full-type-position precondition, different layer.
+fn paren_starts_function_type(bytes: &[u8], paren: usize) -> bool {
+    let mut pos = skip_whitespace_and_comments(bytes, paren + 1);
+    match bytes.get(pos) {
+        // `()` and `(...` are unambiguous.
+        None => return false,
+        Some(b')') => return true,
+        Some(b'.') if bytes[pos..].starts_with(b"...") => return true,
+        _ => {}
+    }
+    // A parameter start: an identifier (covers `this` and contextual keywords),
+    // or a destructuring pattern, which is skipped as a balanced group rather
+    // than parsed — a malformed one only fails the follow-token check below.
+    pos = if is_identifier_start(bytes[pos]) {
+        skip_identifier(bytes, pos)
+    } else if matches!(bytes[pos], b'{' | b'[') {
+        match matching_delimiter_close(bytes, pos) {
+            Some(close) => close + 1,
+            None => return false,
+        }
+    } else {
+        return false;
+    };
+    pos = skip_whitespace_and_comments(bytes, pos);
+    match bytes.get(pos) {
+        // `( xxx :` and `( xxx ,`
+        Some(b':' | b',') => true,
+        // `( xxx ?` — the optional-parameter marker, not `?.` or `??`
+        Some(b'?') => !matches!(bytes.get(pos + 1), Some(b'.' | b'?')),
+        // `( xxx =` — a default value, not `=>` (an arrow) or `==` (equality)
+        Some(b'=') => !matches!(bytes.get(pos + 1), Some(b'>' | b'=')),
+        // `( xxx ) =>`
+        Some(b')') => {
+            let after = skip_whitespace_and_comments(bytes, pos + 1);
+            bytes.get(after) == Some(&b'=') && bytes.get(after + 1) == Some(&b'>')
+        }
+        _ => false,
+    }
+}
+
 /// Check if position starts with an identifier followed by `=>`
 ///
 /// Detects single-parameter arrow functions without parentheses: `x => expr`
@@ -409,7 +554,7 @@ pub(super) fn is_generic_function_type_start(bytes: &[u8], pos: usize) -> bool {
 /// `new Foo()` value (neither of which has `(…) =>`).
 #[inline]
 fn paren_list_then_arrow(bytes: &[u8], paren: usize) -> bool {
-    matching_paren_close(bytes, paren + 1).is_some_and(|paren_close| {
+    matching_delimiter_close(bytes, paren).is_some_and(|paren_close| {
         let after_params = skip_whitespace_and_comments(bytes, paren_close + 1);
         after_params + 1 < bytes.len()
             && bytes[after_params] == b'='
@@ -513,46 +658,46 @@ pub(super) fn matching_angle_close(bytes: &[u8], mut pos: usize) -> Option Option {
-    let mut paren_depth: i32 = 1;
-    let mut bracket_depth: i32 = 0;
-    let mut brace_depth: i32 = 0;
+/// Find the `)`/`]`/`}` closing the `(`/`[`/`{` at `open`, or `None` if a
+/// different delimiter closes unbalanced first or the input ends. Strings,
+/// templates, and comments are opaque, like `matching_angle_close` (and like it,
+/// no regex skip — a `)` inside a regex parameter default could close early,
+/// which only fails toward the shift/comparison reading).
+fn matching_delimiter_close(bytes: &[u8], open: usize) -> Option {
+    // paren, bracket, brace
+    let target = match bytes.get(open)? {
+        b'(' => 0,
+        b'[' => 1,
+        b'{' => 2,
+        _ => return None,
+    };
+    let mut depths = [0i32; 3];
     let end = bytes.len();
+    let mut pos = open;
 
     while pos < end {
         if let Some(past) = skip_trivia(bytes, pos, end, TriviaProfile::JS) {
             pos = past;
             continue;
         }
-        match bytes[pos] {
-            b'(' => paren_depth += 1,
-            b')' => {
-                paren_depth -= 1;
-                if paren_depth == 0 {
+        let slot = match bytes[pos] {
+            b'(' | b')' => Some(0),
+            b'[' | b']' => Some(1),
+            b'{' | b'}' => Some(2),
+            _ => None,
+        };
+        if let Some(slot) = slot {
+            if matches!(bytes[pos], b'(' | b'[' | b'{') {
+                depths[slot] += 1;
+            } else {
+                depths[slot] -= 1;
+                if slot == target && depths[slot] == 0 {
                     return Some(pos);
                 }
-            }
-            b'[' => bracket_depth += 1,
-            b']' => {
-                bracket_depth -= 1;
-                if bracket_depth < 0 {
-                    return None; // Unbalanced - hit array end
+                if depths[slot] < 0 {
+                    return None; // Unbalanced - a different group ended here
                 }
             }
-            b'{' => brace_depth += 1,
-            b'}' => {
-                brace_depth -= 1;
-                if brace_depth < 0 {
-                    return None; // Unbalanced - hit block end
-                }
-            }
-            _ => {}
         }
         pos += 1;
     }
diff --git a/crates/tsv_ts/src/parser/types.rs b/crates/tsv_ts/src/parser/types.rs
index 1e9afd2a3..7be8dfa2f 100644
--- a/crates/tsv_ts/src/parser/types.rs
+++ b/crates/tsv_ts/src/parser/types.rs
@@ -820,7 +820,11 @@ impl<'a, 'arena> Parser<'a, 'arena> {
     ///   or, for a bare single parameter, a `)` then `=>`.
     ///
     /// This mirrors acorn-typescript's `tsIsUnambiguouslyStartOfFunctionType`
-    /// lookahead, which likewise runs only at full-type positions.
+    /// lookahead, which likewise runs only at full-type positions. Its byte-scan
+    /// twin — the same rule asked *before* the parser commits to a type, by the
+    /// arrow-vs-parenthesized-expression lookahead — is
+    /// `expression_lookahead::paren_starts_function_type`; the two must move
+    /// together.
     ///
     /// [`is_definitely_type_start`]: Self::is_definitely_type_start
     fn paren_starts_function_type_params(&mut self, paren_offset: usize) -> bool {
diff --git a/crates/tsv_ts/src/printer/calls/chain_args.rs b/crates/tsv_ts/src/printer/calls/chain_args.rs
index 23e4aa645..dc0083cd2 100644
--- a/crates/tsv_ts/src/printer/calls/chain_args.rs
+++ b/crates/tsv_ts/src/printer/calls/chain_args.rs
@@ -544,7 +544,26 @@ fn build_chain_args_force_expand(
         // (Rule A). The three hugging special cases above all decline once a comment
         // forces expansion, so this is the only argument builder a directive reaches in
         // the chain family.
+        //
+        // A lone expression-body arrow renders its signature flat here, the same way the
+        // expand-last arm does (prettier's `expandLastArg` `removeLines`): the argument is
+        // already broken out onto its own line, so the remaining break belongs after `=>`,
+        // on the body — never inside the parameter list. Without this the signature group
+        // is the first break candidate this hardline-wrapped layout offers, and a
+        // one-parameter arrow shatters into three lines. The non-chain path keeps the
+        // signature intact by construction (`call_formatting.rs`'s `try_single_arg_hug`);
+        // this is the chain path answering the question the same way rather than
+        // differently.
+        let flat_sig = call.arguments.len() == 1
+            && matches!(arg, Expression::ArrowFunctionExpression(arrow)
+                if arrow.body.is_expression() && !is_curried_arrow(arg));
+        if flat_sig {
+            printer.expand_last_arg_flat_params.set(true);
+        }
         arg_parts.push(printer.build_arg_item_doc(paren_open, call.arguments, i));
+        if flat_sig {
+            printer.expand_last_arg_flat_params.set(false);
+        }
 
         // Handle trailing comments and comma placement
         let arg_end = arg.span().end;
diff --git a/crates/tsv_ts/src/printer/chain/adapter.rs b/crates/tsv_ts/src/printer/chain/adapter.rs
index 927272fd3..659e05803 100644
--- a/crates/tsv_ts/src/printer/chain/adapter.rs
+++ b/crates/tsv_ts/src/printer/chain/adapter.rs
@@ -24,22 +24,6 @@ impl<'a> Printer<'a> {
         self.in_for_init.get()
     }
 
-    pub(crate) fn build_parenthesized_base_inner_logical(
-        &self,
-        binary: &internal::BinaryExpression<'_>,
-    ) -> DocId {
-        let d = self.d();
-        let inner = self.build_binary_chain_parts_indented(binary);
-        d.group(inner)
-    }
-
-    pub(crate) fn build_parenthesized_base_inner_binary(
-        &self,
-        binary: &internal::BinaryExpression<'_>,
-    ) -> DocId {
-        self.build_binary_chain_for_parens(binary)
-    }
-
     pub(crate) fn print_call_args(
         &self,
         call: &internal::CallExpression<'_>,
diff --git a/crates/tsv_ts/src/printer/chain/printing.rs b/crates/tsv_ts/src/printer/chain/printing.rs
index 55c8f1714..59aac43f2 100644
--- a/crates/tsv_ts/src/printer/chain/printing.rs
+++ b/crates/tsv_ts/src/printer/chain/printing.rs
@@ -39,25 +39,15 @@ pub(crate) fn print_node_inner<'a>(
                 let inner = printer.build_expression_doc(expr);
                 // Match Prettier: inner group handles indent-on-break,
                 // bare parens outside so chain conditionalGroup drives breaking
+                // Prettier's `group([indent([softline, content]), softline])`: the parens
+                // break onto their own lines and the content stays flat while it fits, so
+                // the chain's conditionalGroup can try flat first. Every base kind below
+                // takes this shape — await, binary, and everything else alike — so it is
+                // written once here rather than repeated per arm.
+                let hang = |content: DocId| {
+                    d.group(d.concat(&[d.indent(d.concat(&[d.softline(), content])), d.softline()]))
+                };
                 let inner_group = match expr {
-                    Expression::AwaitExpression(_) => {
-                        // Prettier: group([indent([softline, inner]), softline])
-                        d.group(
-                            d.concat(&[d.indent(d.concat(&[d.softline(), inner])), d.softline()]),
-                        )
-                    }
-                    Expression::BinaryExpression(binary) if binary.operator.is_logical() => {
-                        // Logical: keep existing indented structure
-                        printer.build_parenthesized_base_inner_logical(binary)
-                    }
-                    Expression::BinaryExpression(binary) => {
-                        // Arithmetic: same indent-on-break as await
-                        let bin_inner = printer.build_parenthesized_base_inner_binary(binary);
-                        d.group(d.concat(&[
-                            d.indent(d.concat(&[d.softline(), bin_inner])),
-                            d.softline(),
-                        ]))
-                    }
                     Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_) => {
                         // IIFE / function callee or arrow member-object: the parens
                         // hug the function — its own body drives breaking, prettier
@@ -66,13 +56,18 @@ pub(crate) fn print_node_inner<'a>(
                         // (`call_formatting.rs`), which wraps with hugging parens.
                         inner
                     }
-                    _ => {
-                        // All other expressions: same indent-on-break as await
-                        // so chain conditionalGroup can try flat first
-                        d.group(
-                            d.concat(&[d.indent(d.concat(&[d.softline(), inner])), d.softline()]),
-                        )
+                    Expression::BinaryExpression(binary) => {
+                        // The chain-for-parens operand doc, so the whole operand chain is
+                        // what stays flat. Every operator family — arithmetic, logical
+                        // (`&&`/`||`), and nullish (`??`) — is laid out identically. A
+                        // logical base used to skip this wrapper and break at its own
+                        // operators instead, welding the closing `).member` onto the last
+                        // operand — a third layout matching neither tsv's arithmetic shape
+                        // nor prettier's. See conformance_prettier.md §TypeScript
+                        // (Parenthesized binary member base).
+                        hang(printer.build_binary_chain_for_parens(binary))
                     }
+                    _ => hang(inner),
                 };
                 // Preserve a comment from the stripped grouping parens inside them,
                 // before `)` (`(x + y /* c */)!.foo`) — prettier relocates it past
diff --git a/crates/tsv_ts/src/printer/expressions/mod.rs b/crates/tsv_ts/src/printer/expressions/mod.rs
index f68d60fbd..3d7ed018e 100644
--- a/crates/tsv_ts/src/printer/expressions/mod.rs
+++ b/crates/tsv_ts/src/printer/expressions/mod.rs
@@ -41,6 +41,17 @@ use tsv_lang::comments_to_emit_in_range;
 use tsv_lang::doc::DocBuf;
 use tsv_lang::doc::arena::DocId;
 
+/// What a comment-blind binary-chain layout builder gets to work with, produced by
+/// `Printer::prepare_binary_chain_layout`.
+enum BinaryChainLayout {
+    /// A finished doc — the comment-aware twin's, or the ordinary binary doc for a chain
+    /// that flattened to a single operand. Return it unchanged.
+    Built(DocId),
+    /// A comment-free chain of 2+ operands, flattened into operand docs plus the
+    /// operators between them, ready to lay out.
+    Operands(DocBuf, OperatorBuf),
+}
+
 impl<'a> Printer<'a> {
     /// Print an expression using doc-based formatting
     pub(crate) fn print_expression(&mut self, expression: &Expression<'_>) {
@@ -1006,23 +1017,14 @@ impl<'a> Printer<'a> {
     /// wants to control the grouping (e.g., chain printing).
     pub(crate) fn build_binary_chain_parts_indented(&self, binary: &BinaryExpression<'_>) -> DocId {
         let d = self.d();
-        // If there are comments within the binary expression, use the comment-aware
-        // implementation from operators.rs which preserves comments and their line breaks.
-        // This handles cases like: fn(a && // comment\n    b)
-        if self.has_comments_to_emit_between(binary.span.start, binary.span.end) {
-            // Use the parts version (no group wrapper) since our caller controls grouping
-            return self.build_binary_chain_parts_with_continuation_indent(binary);
-        }
-
-        // Collect all operands and operators in the chain
-        let mut operands = DocBuf::new();
-        let mut operators = OperatorBuf::new();
-        self.collect_binary_operands_for_indent(binary, &mut operands, &mut operators);
-
-        if operands.len() <= 1 {
-            // Fallback to regular expression doc
-            return self.build_binary_doc(binary);
-        }
+        // The comment-aware twin keeps comments and their line breaks: `fn(a && // c\n b)`.
+        // Continuation-indent style, matching this builder's own.
+        let (operands, operators) = match self.prepare_binary_chain_layout(binary, |p| {
+            p.build_binary_chain_parts_with_continuation_indent(binary)
+        }) {
+            BinaryChainLayout::Built(doc) => return doc,
+            BinaryChainLayout::Operands(operands, operators) => (operands, operators),
+        };
 
         // Build with indented continuations for chains:
         // "first +
@@ -1097,10 +1099,45 @@ impl<'a> Printer<'a> {
         d.concat(&parts)
     }
 
+    /// Prepare a binary chain for one of the two comment-blind layout builders
+    /// (`build_binary_chain_parts_indented`, `build_binary_chain_for_parens`).
+    ///
+    /// Both lay a chain out as operand docs plus raw operator text, with nothing emitted
+    /// in the operand→operator gaps — so a comment anywhere in the chain would be dropped
+    /// (the comment-blind container builder, `docs/comments.md` hazard 4). Routing both
+    /// through this preparation makes the hand-off unskippable: neither can reach an
+    /// operand list without having first answered the comment question. `build_commented`
+    /// names which comment-aware twin this builder's layout wants — they differ in
+    /// indent style, which is the only thing the two callers disagree on.
+    fn prepare_binary_chain_layout(
+        &self,
+        binary: &BinaryExpression<'_>,
+        build_commented: impl FnOnce(&Self) -> DocId,
+    ) -> BinaryChainLayout {
+        if self.has_comments_to_emit_between(binary.span.start, binary.span.end) {
+            return BinaryChainLayout::Built(build_commented(self));
+        }
+
+        let mut operands = DocBuf::new();
+        let mut operators = OperatorBuf::new();
+        self.collect_binary_operands_for_indent(binary, &mut operands, &mut operators);
+
+        if operands.len() <= 1 {
+            // Nothing chained after flattening — the ordinary binary doc says it better.
+            return BinaryChainLayout::Built(self.build_binary_doc(binary));
+        }
+
+        BinaryChainLayout::Operands(operands, operators)
+    }
+
     /// Collect operands and operators from a binary chain (helper for indented version)
     ///
     /// Uses `can_flatten_with()` to determine which operators can be chained together.
     /// Flattens both left and right sides when operators are compatible.
+    ///
+    /// The result carries no spans, so a caller laying it out **cannot** emit comments in
+    /// the gaps between operands — reach it only through `prepare_binary_chain_layout`,
+    /// which answers the comment question first.
     fn collect_binary_operands_for_indent(
         &self,
         expr: &BinaryExpression<'_>,
@@ -1144,23 +1181,29 @@ impl<'a> Printer<'a> {
     /// In break: `a /\nb /\nc` (with outer indent providing indentation)
     pub(crate) fn build_binary_chain_for_parens(&self, binary: &BinaryExpression<'_>) -> DocId {
         let d = self.d();
-        // Collect all operands and operators in the chain
-        let mut operands = DocBuf::new();
-        let mut operators = OperatorBuf::new();
-        self.collect_binary_operands_for_indent(binary, &mut operands, &mut operators);
-
-        if operands.len() <= 1 {
-            // Fallback to regular expression doc
-            return self.build_binary_doc(binary);
-        }
+        // The *ungrouped* comment-aware twin, not the continuation-indent one: this
+        // builder's caller wraps the result in `group(indent([softline, …]), softline)`,
+        // which already supplies the one indent level the broken parens want. A
+        // continuation indent on top of it would indent every operand after the first a
+        // second time.
+        let (operands, operators) = match self
+            .prepare_binary_chain_layout(binary, |p| p.build_binary_chain_doc_ungrouped(binary))
+        {
+            BinaryChainLayout::Built(doc) => return doc,
+            BinaryChainLayout::Operands(operands, operators) => (operands, operators),
+        };
 
-        // For 2-operand non-logical chains, wrap in a group with line() so the
-        // binary can independently decide whether to break at the operator.
-        // The group stays flat when the operands fit; when they don't, line()
-        // fires and breaks at the operator (e.g., `left +\nright`), preventing
-        // the operands' internal break points (like member chain dots) from
-        // firing instead.
-        if operands.len() == 2 && !operators[0].is_logical() {
+        // For 2-operand chains, wrap in a group with line() so the binary can
+        // independently decide whether to break at the operator. The group stays flat
+        // when the operands fit; when they don't, line() fires and breaks at the
+        // operator (e.g., `left +\nright`), preventing the operands' internal break
+        // points (like member chain dots) from firing instead.
+        //
+        // Applies to every operator family. A logical operator used to be excluded here,
+        // which left a parenthesized logical base breaking its operands where the
+        // arithmetic one held them together — see conformance_prettier.md §TypeScript
+        // (Parenthesized binary member base).
+        if operands.len() == 2 {
             return d.group(d.concat(&[
                 operands[0],
                 d.text(" "),
diff --git a/crates/tsv_ts/src/printer/mod.rs b/crates/tsv_ts/src/printer/mod.rs
index bed8aab47..735911b45 100644
--- a/crates/tsv_ts/src/printer/mod.rs
+++ b/crates/tsv_ts/src/printer/mod.rs
@@ -76,7 +76,7 @@ use tsv_lang::{
         arena::{DocArena, DocId},
     },
     has_comments_to_emit_in_range, has_line_comments_in_range, printing,
-    source_scan::{TriviaProfile, skip_trivia},
+    source_scan::{TriviaProfile, is_regex_start, skip_regex_literal, skip_trivia},
 };
 
 /// The parent context that routes a curried arrow chain (`(a) => (b) => …`)
@@ -1091,6 +1091,20 @@ impl<'a> Printer<'a> {
                         return Some((i + 1) as u32);
                     }
                 }
+                // Regex literals are the one trivia kind the shared cursor leaves
+                // significant (it needs a backward token lookback), so they are
+                // skipped here. A regex whose body holds comment bytes — `/\//`,
+                // `/[//]/`, `/\/*/` — would otherwise read as a comment from the
+                // inside and swallow the rest of the line, losing the `)` this
+                // scan is looking for and running its range on to some unrelated
+                // paren. The scan reaches the literal's OPENING `/` first, whose
+                // next byte is never `/` or `*`, so `skip_trivia` can't claim it.
+                b'/' => {
+                    if is_regex_start(source, i, start as usize) {
+                        i = skip_regex_literal(source, i, end);
+                        continue;
+                    }
+                }
                 _ => {}
             }
             i += 1;
diff --git a/crates/tsv_wasm/npm/cli.js b/crates/tsv_wasm/npm/cli.js
index f062291c8..b332645c2 100644
--- a/crates/tsv_wasm/npm/cli.js
+++ b/crates/tsv_wasm/npm/cli.js
@@ -15,7 +15,7 @@ import {
 	readFileSync,
 	realpathSync,
 	statSync,
-	writeFileSync,
+	writeFileSync
 } from 'node:fs';
 import { dirname, isAbsolute, join, relative as path_relative, sep } from 'node:path';
 import { parseArgs } from 'node:util';
@@ -31,7 +31,7 @@ import {
 	parse_typescript_json,
 	parse_typescript_json_no_locations,
 	parse_typescript_json_with_goal,
-	parse_typescript_json_with_goal_no_locations,
+	parse_typescript_json_with_goal_no_locations
 } from './index.js';
 
 /** tsv's native ignore file, discovered hierarchically (one per directory).
@@ -53,13 +53,13 @@ const GITIGNORE_FILE = '.gitignore';
 const FORMATTERS = {
 	svelte: format_svelte,
 	typescript: format_typescript,
-	css: format_css,
+	css: format_css
 };
 
 const PARSERS = {
 	svelte: parse_svelte_json,
 	typescript: parse_typescript_json,
-	css: parse_css_json,
+	css: parse_css_json
 };
 
 // Span-only variant (`--no-locations`): drops per-node `loc` (svelte also `name_loc`).
@@ -67,7 +67,7 @@ const PARSERS = {
 const PARSERS_NO_LOCATIONS = {
 	svelte: parse_svelte_json_no_locations,
 	typescript: parse_typescript_json_no_locations,
-	css: parse_css_json,
+	css: parse_css_json
 };
 
 /** Valid `--parser` values (shared by `format` and `parse` — `FORMATTERS` and
@@ -86,8 +86,7 @@ Commands:
 Run \`tsv  --help\` for command flags.
 `;
 
-const FORMAT_HELP =
-	`Usage: tsv format [] [--check] [--list] [--content  | --stdin] [--parser 

] [--goal ] +const FORMAT_HELP = `Usage: tsv format [] [--check] [--list] [--content | --stdin] [--parser

] [--goal ] Format source code in place (near-Prettier output). @@ -223,9 +222,9 @@ function run_format(args) { check: { type: 'boolean' }, list: { type: 'boolean' }, jobs: { type: 'string' }, - help: { type: 'boolean' }, + help: { type: 'boolean' } }, - FORMAT_HELP, + FORMAT_HELP ); // validated before mode dispatch — a bad value exits 1 in every mode (argh parity) @@ -272,9 +271,10 @@ function format_single(values, positionals, parser, goal) { try { // --goal applies only to the TypeScript parser (svelte is always a module, // css has no goal); svelte/css ignore it, matching the native CLI. - formatted = parser === 'typescript' && goal !== undefined - ? format_typescript_with_goal(input, goal) - : FORMATTERS[parser](input); + formatted = + parser === 'typescript' && goal !== undefined + ? format_typescript_with_goal(input, goal) + : FORMATTERS[parser](input); } catch (error) { eprint(`Parse error: ${error.message}\n`); process.exit(2); @@ -296,9 +296,7 @@ function format_paths(values, positionals) { process.exit(2); } if (values.parser !== undefined) { - eprint( - 'Error: --parser applies to --content/--stdin; file paths use extension detection\n', - ); + eprint('Error: --parser applies to --content/--stdin; file paths use extension detection\n'); process.exit(2); } if (values.goal !== undefined) { @@ -393,9 +391,9 @@ function run_parse(args) { parser: { type: 'string' }, goal: { type: 'string' }, 'no-locations': { type: 'boolean' }, - help: { type: 'boolean' }, + help: { type: 'boolean' } }, - PARSE_HELP, + PARSE_HELP ); // validated before mode dispatch — a bad value exits 1 in every mode (argh parity) @@ -713,7 +711,17 @@ function collect_root(root, cwd, files, errors, warnings) { // scope. Mirrors the native collect_root. if (base_rel !== '' && stack.is_ignored(base_rel, true)) return; - collect_recursive(root, base_rel, true, in_repo, stack, heuristic_active, files, errors, warnings); + collect_recursive( + root, + base_rel, + true, + in_repo, + stack, + heuristic_active, + files, + errors, + warnings + ); } /** Component-wise path ordering matching Rust's `PathBuf` ordering — `/` @@ -734,7 +742,17 @@ function compare_paths(a, b) { return as.length - bs.length; } -function collect_recursive(dir, dir_rel, is_target_root, in_repo, stack, heuristic_active, files, errors, warnings) { +function collect_recursive( + dir, + dir_rel, + is_target_root, + in_repo, + stack, + heuristic_active, + files, + errors, + warnings +) { let entries; try { entries = readdirSync(dir, { withFileTypes: true }); @@ -783,7 +801,7 @@ function collect_recursive(dir, dir_rel, is_target_root, in_repo, stack, heurist dir, in_repo, has_prettierignore, - has_formatignore, + has_formatignore ); if (warning != null) warnings.push(warning); } @@ -795,7 +813,7 @@ function collect_recursive(dir, dir_rel, is_target_root, in_repo, stack, heurist dir, in_repo, has_prettierignore, - has_formatignore, + has_formatignore ); if (warning != null) warnings.push(warning); } @@ -832,7 +850,17 @@ function collect_recursive(dir, dir_rel, is_target_root, in_repo, stack, heurist continue; } // the child reads its own ignore files when we recurse into it - collect_recursive(path, child_rel, false, in_repo, stack, child_heuristic, files, errors, warnings); + collect_recursive( + path, + child_rel, + false, + in_repo, + stack, + child_heuristic, + files, + errors, + warnings + ); } else if (entry.isFile() && stack.should_format_file(entry.name, child_rel)) { files.push(path); } diff --git a/crates/tsv_wasm/npm/locations.d.ts b/crates/tsv_wasm/npm/locations.d.ts index 25789eeb7..3ea3f46e0 100644 --- a/crates/tsv_wasm/npm/locations.d.ts +++ b/crates/tsv_wasm/npm/locations.d.ts @@ -55,7 +55,11 @@ export declare function create_locator(source: string, opts?: LocationOptions): * and returns it. Exact for TypeScript, approximate for Svelte (`name_loc` itself * is exact), a no-op for CSS. */ -export declare function reconstruct_locations(ast: any, source: string, opts?: LocationOptions): any; +export declare function reconstruct_locations( + ast: any, + source: string, + opts?: LocationOptions +): any; /** * Line/column for a single node. Rebuilds the line-start table per call — reuse a diff --git a/crates/tsv_wasm/npm/locations.js b/crates/tsv_wasm/npm/locations.js index 8378a8c87..1a8d9c7ee 100644 --- a/crates/tsv_wasm/npm/locations.js +++ b/crates/tsv_wasm/npm/locations.js @@ -199,7 +199,7 @@ const NAME_LOC_KINDS = new Map([ ['TransitionDirective', 'directive'], ['AnimateDirective', 'directive'], ['LetDirective', 'directive'], - ['Attribute', 'attribute'], + ['Attribute', 'attribute'] ]); /** @@ -429,7 +429,7 @@ function walk_add_loc(value, ctx) { if (span) { value.name_loc = { start: name_loc_at(span[0], ctx.starts), - end: name_loc_at(span[1], ctx.starts), + end: name_loc_at(span[1], ctx.starts) }; } if (ctx.elements !== null && NAME_LOC_KINDS.get(value.type) === 'element') { @@ -491,7 +491,7 @@ export function create_locator(source, opts) { }, reconstruct(ast) { return reconstruct_in(ast, starts, source, language); - }, + } }; } diff --git a/crates/tsv_wasm/types/tsv_ast.d.ts b/crates/tsv_wasm/types/tsv_ast.d.ts index 707c9bd9c..5eb1d6622 100644 --- a/crates/tsv_wasm/types/tsv_ast.d.ts +++ b/crates/tsv_wasm/types/tsv_ast.d.ts @@ -766,11 +766,7 @@ export interface ClassBody { body: ClassMember[]; } -export type ClassMember = - | MethodDefinition - | PropertyDefinition - | StaticBlock - | TSIndexSignature; +export type ClassMember = MethodDefinition | PropertyDefinition | StaticBlock | TSIndexSignature; /** Static initialization block: `static { ... }` (ES2022). */ export interface StaticBlock { @@ -1053,11 +1049,7 @@ export interface ExportDefaultDeclaration { } export type ExportDefaultValue = - | Expression - | FunctionDeclaration - | TSDeclareFunction - | ClassDeclaration - | TSInterfaceDeclaration; + Expression | FunctionDeclaration | TSDeclareFunction | ClassDeclaration | TSInterfaceDeclaration; /** * A module export name: a bare `Identifier` or a string `Literal`. Per ecma262 @@ -1125,9 +1117,7 @@ export interface ImportDeclaration { } export type ImportSpecifier = - | ImportDefaultSpecifier - | ImportNamedSpecifier - | ImportNamespaceSpecifier; + ImportDefaultSpecifier | ImportNamedSpecifier | ImportNamespaceSpecifier; export interface ImportDefaultSpecifier { type: 'ImportDefaultSpecifier'; diff --git a/deno.json b/deno.json index 57464d72e..946199f0e 100644 --- a/deno.json +++ b/deno.json @@ -25,7 +25,8 @@ "typecheck": "cargo check --workspace", "test": "cargo test --workspace", "lint": "cargo clippy --workspace --all-features --all-targets -- -D warnings", - "check": "cargo fmt --check && deno task pins:audit && deno task typecheck && deno task conformance:audit && deno task conformance:audit:compiler && deno task scan:audit && deno task fanout:audit && deno task roundtrip:audit && deno task canonicalize:audit && deno task binding:audit && deno task authoring:audit && deno task fuzz:audit && deno task test:deno && deno task test && deno task test:audits && deno task swallow:audit && deno task comments:audit && deno task gaps:audit && deno task blanks:audit && deno task ignore:audit && deno task check:ast-types && deno task lint", + "format:audit": "cargo run --profile corpus -p tsv_cli --quiet -- format --check .", + "check": "cargo fmt --check && deno task format:audit && deno task pins:audit && deno task typecheck && deno task conformance:audit && deno task conformance:audit:compiler && deno task scan:audit && deno task fanout:audit && deno task roundtrip:audit && deno task canonicalize:audit && deno task binding:audit && deno task authoring:audit && deno task fuzz:audit && deno task test:deno && deno task test && deno task test:audits && deno task swallow:audit && deno task comments:audit && deno task gaps:audit && deno task blanks:audit && deno task ignore:audit && deno task check:ast-types && deno task lint", "check:ast-types": "deno run --allow-read --allow-write=scripts --allow-run=cargo,deno scripts/check_ast_types.ts", "doctor": "deno run --allow-read --allow-sys --allow-run=deno,node,bun,cargo,wasm-pack,npm --config benches/js/deno.json scripts/doctor.ts", diff --git a/docs/audits.md b/docs/audits.md index d54ede316..762797703 100644 --- a/docs/audits.md +++ b/docs/audits.md @@ -17,6 +17,7 @@ The Svelte compiler's *sidecar-dependent* harnesses — the corpus comparison, t | [Ignore honoring](#ignore-directive-honoring-audit-ignoreaudit) | `ignore:audit` | `prettier-ignore` positions that silently reformat an ignored node, misbind a trailing directive, over-freeze, or lose the freeze on pass 2 | `deno task check` (ratchet) | | [Build fanout](#build-fanout-audit-fanoutaudit) | `fanout:audit` | exponential doc-node rebuild in nested layout candidates | `deno task check` | | [Raw-find scan](#raw-find-scan-audit-scanaudit) | `scan:audit` | new raw substring scans over source (comment-blind delimiter matching) | `deno task check` | +| [Self-format](#self-format-audit-formataudit) | `format:audit` | tsv failing to format its OWN TS/JS — a would-change file (non-idempotency) or a parse error (over-rejection) | `deno task check` | | [Authoring independence](#authoring-independence-audit-authoringaudit) | `authoring:audit` | two render-equivalent authorings settling on two fixed points; non-idempotency | `deno task check` | | [Round-trip](#formatreparse-round-trip-audit-roundtripaudit) | `roundtrip:audit` | formatted output the parser rejects (delimiter/structure corruption) | `deno task check` | | [Binding](#commenttoken-binding-audit-bindingaudit) | `binding:audit` | a glued comment re-bound to a different subtree by a migrating paren | `deno task check` | @@ -211,6 +212,20 @@ cargo run -p tsv_debug build_fanout_audit # Also: --json. Gated in `deno task check` via the `fanout:audit` task. ``` +**What it is blind to.** The audit measures how the doc-node count *grows with depth*, so it +sees only violations that **compound**. Two shapes escape it: + +- **A flat constant factor.** An eager build a later branch discards — `let x = build_…(…)` + bound before a `match`/`if` whose arms don't all use `x` — costs a fixed 2× at that one site + and never compounds, so no depth curve can separate it from the baseline. The tell is + syntactic, not empirical; grep the shape rather than waiting for a profile, since a single + site sits below the noise floor of a corpus measurement while still being free to remove. +- **Breadth.** A quadratic over a flat sibling sequence (`[..i].iter().any(…)` inside a child + loop) is likewise structurally invisible — it grows with sibling count, not nesting. + +Both are guarded by review and grep, not by this audit. The known open instance of the first is +a parenthesized binary chain base; see the `chain/printing.rs` note in the perf queue. + ## Raw-Find Scan Audit (`scan:audit`) ```bash @@ -230,6 +245,44 @@ cargo run -p tsv_debug scan_audit --list # enumerate every scan site # byte-loops (the cursor is their sanctioned home). ``` +## Self-Format Audit (`format:audit`) + +```bash +# tsv formats its own TS/JS. Runs `tsv format --check .` over the repo, so the +# formatter's own output on real, non-fixture source is a standing gate. +deno task format:audit +``` + +**What it proves.** Two things at once, and it fails on either: + +- **exit 1 — a would-change file**: the committed tree is not a `tsv format` fixed + point. Because the tree is committed formatted, this also makes the audit an + **idempotency** check on real code: a non-idempotent file can never be committed + clean, so it shows up here rather than as silent churn. +- **exit 2 — a parse error**: tsv rejects a file it must be able to read. This is + the signal with no other home — every over-rejection the repo's own TS has hit + was found here first. + +**Why it is not redundant with the corpus gates.** `corpus:compare:format` and the +conformance gates read **other** repositories (framework checkouts, prettier +suites, the live dev repos). None of them read tsv's own `benches/js`, `scripts`, +or `crates/**/npm` sources, so nothing else covers this tree. + +**Scope.** The root `.formatignore` prunes `tests/fixtures/` and +`tests/fixtures_compile/` — those files are DATA whose whole claim is the state +they are committed in, and many are deliberately not format fixed points. A +directory argument can never step past that; only an explicitly named FILE bypasses +the ignore files, which is why nothing under `tests/` may ever be named directly. +Markdown and JSON are out of scope entirely — they stay hand-maintained. + +**Blind spots.** It only sees shapes this repo's own source happens to contain, so +it is a dogfooding tripwire, not a survey — a formatter bug in a construct tsv's +own TS never writes stays invisible here. The injection audits (`gaps:audit`, +`blanks:audit`) and the corpus gates are the discovery arms. + +**Build world.** Runs `tsv_cli` under `--profile corpus`, the single build world +every `deno task check` audit shares, so it adds no separate compile. + ## Authoring-Independence Audit (`authoring:audit`) ```bash diff --git a/docs/comments.md b/docs/comments.md index db10d28d3..8e390d9d3 100644 --- a/docs/comments.md +++ b/docs/comments.md @@ -57,6 +57,8 @@ Two corollaries worth naming, because each was a whole family of bugs: 4. **A builder that emits only its CHILDREN's docs prints no gap comment at all** — the positional analog of hazard 1, and the one hazard that needs no ownership to bite. An *alternate-layout* container builder (a forced-hardline twin of the real one, reached when a caller needs a specific shape for its `fits()` measurement) loops over the properties or elements and pushes each child's doc — and never runs a gap lookup, so the `{`/`[`→first-item gap, the inter-item gaps, the trailing gap before `}`/`]`, and a dangling comment in an *empty* container are all DROPPED. `build_object_doc_expanded` / `build_array_doc_expanded` did this to every one of those positions; `build_array_group_doc_forced` did it to the trailing side. The fix is to hand a commented container to the comment-aware twin wholesale — the gate must precede the **empty**-container arm, or the dangling case stays open — or to share the per-item emission seam (`push_array_element_with_inline_comments`). **Ownership masks this one too, in mirror image to hazard 3**: a glued *leading* block is owned and rides inside its item's doc, so it survives; only the trailing and own-line comments are lost, and a leading-comment repro will report the builder healthy. + The container builders are not the only family. An **operator-chain layout builder** has the identical shape: it flattens a chain into operand docs plus raw operator *text*, so every operand→operator and operator→operand gap is unemitted — `(a /* c */ && b)`, `(a || // c⏎b)`. `tsv_ts`'s two are `build_binary_chain_parts_indented` and `build_binary_chain_for_parens`; both reach their operand list only through `prepare_binary_chain_layout`, which answers the comment question first and hands a commented chain to the comment-aware twin (`operators.rs`, which collects operands *with spans* — the structural difference that makes gap emission possible at all). Which twin depends on the caller's indent: a builder whose caller already wraps it in `group(indent([softline, …]), softline)` wants the **ungrouped** twin, not the continuation-indent one, or every operand after the first indents twice. When adding a chain-layout builder, route it through the same preparation rather than calling the span-less collector directly. + All four hazards are what the **print-once comment ledger** exists to catch — the structural guard on this model: every comment a document parses must be emitted exactly once, or the audit reports it as DROPPED or DOUBLE-PRINTED (`deno task comments:audit`, gated in `deno task check`; see [audits.md §Comment Ledger Audit](./audits.md#comment-ledger-audit-commentsaudit)). Nothing else in the detached model forces a parsed comment to reach the output. Hazard 3 was found by it, not by review — the block-comment repro looked clean. But the ledger only sees a document **as authored**, so it cannot see a *wholly* comment-blind builder until some file happens to put a comment where that builder prints: hazard 4 stayed green on `comments:audit` across zzz / gro / fuz_app / fuz_ui, prettier's TS+JS suites, and svelte's own source, because the shape simply does not occur there. The **injection** audits are the discovery arm for that class — `gaps:audit` had it pinned as sixteen known DROPPED shapes all along. When you add or change an alternate-layout builder, the ledger passing is not evidence; put a comment in each of its gaps yourself. diff --git a/docs/conformance_prettier.md b/docs/conformance_prettier.md index 1e9c72330..1114c1d5a 100644 --- a/docs/conformance_prettier.md +++ b/docs/conformance_prettier.md @@ -642,6 +642,7 @@ Same layout inside an inline element (head wraps + body expands, element hugs th - Trailing member after object-arg call — ◆print_width — [trailing_member_expand_args_long](../tests/fixtures/typescript/expressions/calls/chained/trailing_member_expand_args_long_prettier_divergence/) - Instantiation expression parens — ◆prettier_bug — [instantiation_parens](../tests/fixtures/typescript/typescript_specific/assertions/instantiation_parens_prettier_divergence/), [export_default_instantiation](../tests/fixtures/typescript/modules/exports/default_wrappable_leftmost_operators/instantiation_prettier_divergence/) - Non-null parenthesized base — ◆design_choice — [non_null_paren_base_long](../tests/fixtures/typescript/expressions/member/non_null_paren_base_long_prettier_divergence/) +- Parenthesized binary member base — ◆design_choice ◆print_width — [paren_binary_base_long](../tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/) - Constrained infer extends-operand parens — ◆prettier_bug — [constrained_extends_parens](../tests/fixtures/typescript/types/infer/constrained_extends_parens_prettier_divergence/) - Arrow type param trailing comma — ◆design_choice — [single_type_param](../tests/fixtures/typescript/expressions/arrow/generic/single_type_param_prettier_divergence/) - Empty-object comment bracket spacing — ◆design_choice — [empty_block_comment](../tests/fixtures/typescript/expressions/objects/empty_block_comment_prettier_divergence/), [destructure empty_comment](../tests/fixtures/typescript/expressions/destructuring/empty_comment_prettier_divergence/), [enum empty_comment](../tests/fixtures/typescript/declarations/enum/body_empty_comment_prettier_divergence/), [literal_body_empty](../tests/fixtures/typescript/types/comments/literal_body_empty_prettier_divergence/), [union_empty_object_member](../tests/fixtures/typescript/types/union_empty_object_member_prettier_divergence/), [call_type_arg_empty_comment](../tests/fixtures/typescript/typescript_specific/generics/call_type_arg_empty_comment_prettier_divergence/) @@ -668,6 +669,8 @@ tsv treats these like any other function call—no special-casing for module pat **Non-null parenthesized base**: For a non-null assertion on a parenthesized base whose inner call breaks its arguments (`(await call(...))!.member`), Prettier hugs the inner call (`(await call(\n...\n))!.member`) — yet it _hangs_ the outer parens for the same base without the `!` (`(await call(...)).member`, see [paren_base_trailing_long](../tests/fixtures/typescript/expressions/member/paren_base_trailing_long/), where tsv matches Prettier). tsv lays out the parenthesized base the same way regardless of a trailing non-null assertion, keeping the two forms visually consistent. Content is identical (ASTs match); only the parenthesized-base layout differs. +**Parenthesized binary member base**: For a parenthesized binary expression used as a member-access base, long enough that the parens must break onto their own lines and whose left operand is itself a parenthesized binary (`((a && b) || c).toString()`), Prettier breaks the parens **and** splits the operand chain onto separate lines. tsv takes only the break the width demands — the parens break, the operand chain stays flat while it fits (`(\n\t(a && b) || c\n).toString()`) — per [§Print Width Philosophy](#print-width-philosophy). The shape is uniform along both axes Prettier varies on: **operator family** (arithmetic, logical `&&`/`||`, and nullish `??` bases are laid out identically) and **what follows the base** (a plain `.member`, a non-null `!.member`, and an optional `?.member` all lay out identically, where Prettier hangs the parens for `.`/`?.` and welds `)!.member` onto the last operand for `!` — the same stance as [§Non-null parenthesized base](#typescript), extended from call/await bases to binary ones). That uniformity is the substance of the rule: the alternative — giving logical and nullish bases a *third* layout that welds the closing `).member` onto the last operand (`((a && b) ||\n\tc).toString()`) — matches neither tsv's own arithmetic shape nor Prettier's, so it is a shape with no constituency. A **flat** chain (`a && b && c`, no parenthesized operand) is outside the divergence: with no nested operand to hold together both formatters break every operand. Content is identical (ASTs match); only the operand-chain layout differs. See [paren_binary_base_long](../tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/). A parenthesized **call** or `as`-cast base is a different case, where tsv matches Prettier — see [paren_base_trailing_long](../tests/fixtures/typescript/expressions/member/paren_base_trailing_long/). + **Non-null assertion in optional-chain parens**: A non-null `!` on a **bare** parenthesized optional chain — one with no trailing non-optional access — has redundant parens: the `!` is TypeScript-only and applies to the whole chain regardless (`(a?.b)!` ≡ `a?.b!`), so tsv strips them (`(a?.b)!` → `a?.b!`, and the `!`-inside form `(a?.b!)` → `a?.b!`). Prettier keeps the parens on `(a?.b)!` (it strips only `(a?.b!)`). tsv matches Biome. When a **non-optional access follows** (`(a?.b)!.c`), the parens are required — they seal the chain so `.c` isn't short-circuited (`(a?.b)!.c` vs `a?.b!.c`) — and there both formatters keep the parens and preserve the author's `!` placement (inside or outside: `(a?.b!).c` and `(a?.b)!.c` each stay as written), so that case is not a divergence. Content is identical (ASTs match); only the redundant parens differ. Fixtures: [optional_paren_non_null_bare](../tests/fixtures/typescript/expressions/chain/optional_paren_non_null_bare_prettier_divergence/) (the strip), [optional_paren_non_null_boundary](../tests/fixtures/typescript/expressions/chain/optional_paren_non_null_boundary/) and [optional_paren_non_null_inside](../tests/fixtures/typescript/expressions/chain/optional_paren_non_null_inside/) (required parens, both preserved). **Return type generic union**: Prettier has special handling for `null` and `void` in union types within generic return types. When the second union member is `null` or `void`: (1) function declarations and class methods allow lines to exceed print width instead of breaking inside `<>`, (2) arrow functions break the assignment (`const fn =`) instead of breaking inside the return type. tsv breaks consistently inside the return type generic at the print width boundary regardless of type keyword. diff --git a/scripts/check_ast_types.ts b/scripts/check_ast_types.ts index b9bd86f19..314802298 100644 --- a/scripts/check_ast_types.ts +++ b/scripts/check_ast_types.ts @@ -32,7 +32,7 @@ const samples: Sample[] = [ name: 'ts_var_with_type_annotation', source: 'const x: number = 1;\nlet y = "two";', parser: 'typescript', - type: 'Program', + type: 'Program' }, { name: 'ts_optional_and_predicate_methods', @@ -43,10 +43,10 @@ const samples: Sample[] = [ 'declare class C {', ' isA?(x: unknown): x is A;', ' g(x: T): T;', - '}', + '}' ].join('\n'), parser: 'typescript', - type: 'Program', + type: 'Program' }, { name: 'ts_function_class_import', @@ -58,16 +58,16 @@ const samples: Sample[] = [ 'class C extends Base implements I {', ' #priv: T;', ' constructor(public override readonly x: T) { super(); this.#priv = x; }', - '}', + '}' ].join('\n'), parser: 'typescript', - type: 'Program', + type: 'Program' }, { name: 'ts_directive_prologue', source: '"use strict";\nlet a = 1;', parser: 'typescript', - type: 'Program', + type: 'Program' }, { // `export default interface` — the TSInterfaceDeclaration member of the @@ -75,7 +75,7 @@ const samples: Sample[] = [ name: 'ts_export_default_interface', source: 'export default interface A {\n\ta: string;\n}', parser: 'typescript', - type: 'Program', + type: 'Program' }, { // Import attributes: bare `Identifier` key and quoted `Literal` key @@ -85,10 +85,10 @@ const samples: Sample[] = [ 'import a from "./a" with { type: "json" };', 'import b from "./b" with { "resolution-mode": "import" };', 'export { c } from "./c" with { type: "json" };', - 'export * from "./d" with { type: "json" };', + 'export * from "./d" with { type: "json" };' ].join('\n'), parser: 'typescript', - type: 'Program', + type: 'Program' }, { // Over-rejection fixes: export-import-equals (`TSImportEqualsDeclaration` @@ -101,10 +101,10 @@ const samples: Sample[] = [ 'export as namespace Lib;', '(x as T) = 1;', '(y as U) += 2;', - '[z as V] = arr;', + '[z as V] = arr;' ].join('\n'), parser: 'typescript', - type: 'Program', + type: 'Program' }, { // String module specifiers (ES2022 `ModuleExportName : IdentifierName | @@ -115,10 +115,10 @@ const samples: Sample[] = [ 'import { "str a" as b } from "./a";', 'export { c as "str c" } from "./c";', 'export { "str d" as "str e" } from "./d";', - 'export * as "str f" from "./f";', + 'export * as "str f" from "./f";' ].join('\n'), parser: 'typescript', - type: 'Program', + type: 'Program' }, { // Optional destructuring-pattern parameters (`[]?` / `{}?` / `[a]?: T`): @@ -131,10 +131,10 @@ const samples: Sample[] = [ '\tn({}?): void;', '\to([a]?: number[]): void;', '}', - 'type F = ([]?) => void;', + 'type F = ([]?) => void;' ].join('\n'), parser: 'typescript', - type: 'Program', + type: 'Program' }, { // Parameter decorators on non-identifier bindings — the `decorators` field @@ -146,16 +146,16 @@ const samples: Sample[] = [ 'class A {', '\tconstructor(@d private a: number = 1) {}', '\tm(@d { a }: T, @e [b]: U, @f c = 1) {}', - '}', + '}' ].join('\n'), parser: 'typescript', - type: 'Program', + type: 'Program' }, { name: 'css_rule_at_media', source: '.foo { color: red; }\n@media (min-width: 600px) {\n\t.bar { padding: 1em 2em; }\n}', parser: 'css', - type: 'StyleSheetFile', + type: 'StyleSheetFile' }, { name: 'svelte_script_element_style', @@ -168,10 +168,10 @@ const samples: Sample[] = [ '', '', + '' ].join('\n'), parser: 'svelte', - type: 'Root', + type: 'Root' }, { name: 'svelte_blocks_and_directives', @@ -191,11 +191,11 @@ const samples: Sample[] = [ '\t{value}', '{:catch err}', '\t{err}', - '{/await}', + '{/await}' ].join('\n'), parser: 'svelte', - type: 'Root', - }, + type: 'Root' + } ]; async function parse(sample: Sample): Promise { @@ -210,10 +210,10 @@ async function parse(sample: Sample): Promise { '--content', sample.source, '--parser', - sample.parser, + sample.parser ], stdout: 'piped', - stderr: 'piped', + stderr: 'piped' }); const { code, stdout, stderr } = await cmd.output(); if (code !== 0) { @@ -230,7 +230,7 @@ const header = [ '// Auto-generated by scripts/check_ast_types.ts — do not edit.', '// Tests that tsv_ast.d.ts accepts the shapes produced by `tsv parse`.', "import type { Program, Root, StyleSheetFile } from '../crates/tsv_wasm/types/tsv_ast.d.ts';", - '', + '' ]; console.log(`Parsing ${samples.length} sample(s)...`); @@ -245,7 +245,7 @@ console.log(`Running \`deno check ${gen_path}\`...`); const check = new Deno.Command('deno', { args: ['check', gen_path], stdout: 'inherit', - stderr: 'inherit', + stderr: 'inherit' }); const { code } = await check.output(); diff --git a/scripts/check_canonical_pins.ts b/scripts/check_canonical_pins.ts index bd9fcab8d..acd45a7bd 100644 --- a/scripts/check_canonical_pins.ts +++ b/scripts/check_canonical_pins.ts @@ -45,7 +45,7 @@ const CANONICAL_PACKAGES = [ 'prettier-plugin-svelte', 'svelte', 'acorn', - '@sveltejs/acorn-typescript', + '@sveltejs/acorn-typescript' ] as const; const SIDECAR_PATH = 'crates/tsv_debug/src/deno/sidecar.ts'; @@ -84,7 +84,7 @@ for (const name of CANONICAL_PACKAGES) { versions_object: new RegExp(`(?:'${escaped}'|${escaped}): '([^']+)'`).exec(versions_block)?.[1] ?? null, import_specifier: new RegExp(`npm:${escaped}@(\\d+\\.\\d+\\.\\d+)`).exec(sidecar)?.[1] ?? null, - bench_package_json: bench_deps[name] ?? null, + bench_package_json: bench_deps[name] ?? null }; const entries = Object.entries(pins) as [keyof PinSources, string | null][]; @@ -95,9 +95,7 @@ for (const name of CANONICAL_PACKAGES) { } const distinct = new Set(entries.map(([, v]) => v)); if (distinct.size > 1) { - failures.push( - `${name}: pins disagree — ${entries.map(([k, v]) => `${k}=${v}`).join(', ')}`, - ); + failures.push(`${name}: pins disagree — ${entries.map(([k, v]) => `${k}=${v}`).join(', ')}`); continue; } report.push(`${name}@${pins.versions_object}`); @@ -110,7 +108,7 @@ for (const name of CANONICAL_PACKAGES) { failures.push(`acorn: missing npm:acorn@x.y.z import-map pin in ${ACTOR_PATH}`); } else if (actor_pin !== pins.versions_object) { failures.push( - `acorn: ${ACTOR_PATH} import-map pin ${actor_pin} disagrees with ${pins.versions_object}`, + `acorn: ${ACTOR_PATH} import-map pin ${actor_pin} disagrees with ${pins.versions_object}` ); } } @@ -118,9 +116,12 @@ for (const name of CANONICAL_PACKAGES) { // --- Checkout alignment (see the header docstring, half 2) --------------------- -const CHECKOUT_ALIGNMENT: { pkg_json: string; npm_package: (typeof CANONICAL_PACKAGES)[number] }[] = [ +const CHECKOUT_ALIGNMENT: { + pkg_json: string; + npm_package: (typeof CANONICAL_PACKAGES)[number]; +}[] = [ { pkg_json: '../svelte/packages/svelte/package.json', npm_package: 'svelte' }, - { pkg_json: '../acorn-typescript/package.json', npm_package: '@sveltejs/acorn-typescript' }, + { pkg_json: '../acorn-typescript/package.json', npm_package: '@sveltejs/acorn-typescript' } ]; const checkout_notes: string[] = []; for (const { pkg_json, npm_package } of CHECKOUT_ALIGNMENT) { @@ -136,7 +137,7 @@ for (const { pkg_json, npm_package } of CHECKOUT_ALIGNMENT) { if (checkout_version !== pinned) { failures.push( `${npm_package}: checkout ${pkg_json} is v${checkout_version ?? '?'} but the oracle pin is v${pinned} — ` + - 'align the checkout to the pinned tag, or bump the canonical pins deliberately (a fixture re-baseline)', + 'align the checkout to the pinned tag, or bump the canonical pins deliberately (a fixture re-baseline)' ); } } @@ -149,7 +150,7 @@ const head_commit = (repo: string): string | null => { const { success, stdout } = new Deno.Command('git', { args: ['-C', repo, 'rev-parse', 'HEAD'], stdout: 'piped', - stderr: 'null', + stderr: 'null' }).outputSync(); return success ? new TextDecoder().decode(stdout).trim() : null; } catch { @@ -166,7 +167,9 @@ for (const [repo, { commit, pins }] of Object.entries(GATE_CHECKOUT_COMMITS)) { } // The recorded commits are abbreviated, so compare on the prefix. if (!head.startsWith(commit)) { - drifted.push(`${repo}: measured at ${commit}, now at ${head.slice(0, commit.length)} — pins: ${pins}`); + drifted.push( + `${repo}: measured at ${commit}, now at ${head.slice(0, commit.length)} — pins: ${pins}` + ); } } @@ -175,7 +178,7 @@ if (failures.length > 0) { for (const f of failures) console.error(` · ${f}`); console.error( ` Pin sites: ${SIDECAR_PATH} (VERSIONS + imports), ${BENCH_PKG_PATH}, ${ACTOR_PATH} — edit in lockstep.\n` + - ' ⚠ Bumping a canonical pin re-baselines the fixture corpus — see benches/js/CLAUDE.md §"Canonical baseline is coupled".', + ' ⚠ Bumping a canonical pin re-baselines the fixture corpus — see benches/js/CLAUDE.md §"Canonical baseline is coupled".' ); Deno.exit(1); } @@ -184,10 +187,10 @@ if (drifted.length > 0) { for (const d of drifted) console.warn(` · ${d}`); console.warn( ' The count pins are the gate; this is the diagnosis. If one trips, suspect corpus movement\n' + - ' before a tsv regression — and re-record the commit in GATE_CHECKOUT_COMMITS when you re-pin.', + ' before a tsv regression — and re-record the commit in GATE_CHECKOUT_COMMITS when you re-pin.' ); } console.log( `pins:audit OK — canonical pins agree across sidecar VERSIONS/imports, benches/js/package.json, actor.rs ` + - `(${report.join(', ')}); checkouts aligned${checkout_notes.length > 0 ? ` (${checkout_notes.join('; ')})` : ''}`, + `(${report.join(', ')}); checkouts aligned${checkout_notes.length > 0 ? ` (${checkout_notes.join('; ')})` : ''}` ); diff --git a/scripts/doctor.ts b/scripts/doctor.ts index 66a4df8a2..adfbbf1e8 100644 --- a/scripts/doctor.ts +++ b/scripts/doctor.ts @@ -77,7 +77,7 @@ function read_pkg_version(path: string): string | null { const strict = Deno.args.includes('--strict'); console.log( - `tsv doctor — diagnostic-toolchain setup check (read-only${strict ? ', --strict: warnings fail' : ''})`, + `tsv doctor — diagnostic-toolchain setup check (read-only${strict ? ', --strict: warnings fail' : ''})` ); // --- Runtimes ----------------------------------------------------------------- @@ -87,12 +87,17 @@ ok(`deno ${Deno.version.deno}`); const node_version = run_version('node'); if (node_version === null) { - warn('node missing — bench:node, bench:install, test:npm, and the publish artifact tests need it'); + warn( + 'node missing — bench:node, bench:install, test:npm, and the publish artifact tests need it' + ); } else { const m = /^v(\d+)\.(\d+)/.exec(node_version); - const new_enough = m !== null && (Number(m[1]) > 22 || (Number(m[1]) === 22 && Number(m[2]) >= 18)); + const new_enough = + m !== null && (Number(m[1]) > 22 || (Number(m[1]) === 22 && Number(m[2]) >= 18)); if (new_enough) ok(`node ${node_version} (≥ 22.18)`); - else warn(`node ${node_version} — < 22.18 lacks native TS type-stripping; the harness entries fail to parse`); + else warn( + `node ${node_version} — < 22.18 lacks native TS type-stripping; the harness entries fail to parse` + ); } const bun_version = run_version('bun'); @@ -105,7 +110,9 @@ else ok(cargo_version); const wasm_pack_version = run_version('wasm-pack'); if (wasm_pack_version === null) { - warn('wasm-pack missing — WASM builds (build:wasm:*, bench, publish) unavailable: cargo install wasm-pack'); + warn( + 'wasm-pack missing — WASM builds (build:wasm:*, bench, publish) unavailable: cargo install wasm-pack' + ); } else ok(wasm_pack_version); const npm_version = run_version('npm'); @@ -118,7 +125,7 @@ section('Canonical oracle pins'); const pins = new Deno.Command('deno', { args: ['run', '--allow-read', 'scripts/check_canonical_pins.ts'], stdout: 'piped', - stderr: 'piped', + stderr: 'piped' }).outputSync(); const pins_out = new TextDecoder().decode(pins.success ? pins.stdout : pins.stderr).trim(); if (pins.success) ok(pins_out); @@ -142,7 +149,7 @@ if (exists('../svelte/packages/svelte/tests')) { if (v !== null && v !== versions.canonical.svelte) { warn( `../svelte checkout is v${v} but the svelte oracle is pinned v${versions.canonical.svelte} — ` + - 'suite inputs and the grading parser disagree (align the checkout or bump the pins deliberately)', + 'suite inputs and the grading parser disagree (align the checkout or bump the pins deliberately)' ); } else ok(`../svelte checkout (v${v ?? '?'}, matches the pin)`); } else warn('../svelte checkout missing — conformance:svelte-fixtures + the corpus suites need it'); @@ -152,7 +159,7 @@ if (exists('../acorn-typescript/test')) { if (v !== null && v !== versions.canonical['@sveltejs/acorn-typescript']) { warn( `../acorn-typescript checkout is v${v} but the oracle is pinned ` + - `v${versions.canonical['@sveltejs/acorn-typescript']} — suite inputs and the grading parser disagree`, + `v${versions.canonical['@sveltejs/acorn-typescript']} — suite inputs and the grading parser disagree` ); } else ok(`../acorn-typescript checkout (v${v ?? '?'}, matches the pin)`); } else warn('../acorn-typescript checkout missing — conformance:ts-fixtures needs it'); @@ -160,7 +167,9 @@ if (exists('../acorn-typescript/test')) { if (exists('../typescript/tests/baselines/reference')) { ok('../typescript checkout (baselines present)'); } else if (exists('../typescript')) { - warn('../typescript present but tests/baselines/reference missing — conformance:ts-repo will FAIL (partial checkout)'); + warn( + '../typescript present but tests/baselines/reference missing — conformance:ts-repo will FAIL (partial checkout)' + ); } else warn('../typescript checkout missing — conformance:ts-repo needs it'); // Informational (NOT gated by pins:audit — see its docstring): the prettier @@ -171,13 +180,17 @@ if (exists('../prettier')) { if (v !== null && v.replace(/-dev$/, '') !== versions.canonical.prettier) { warn( `../prettier checkout is v${v} vs pinned prettier v${versions.canonical.prettier} — its fixture ` + - 'suites (corpus inputs) come from a different version than the live oracle (informational)', + 'suites (corpus inputs) come from a different version than the live oracle (informational)' ); } else ok(`../prettier checkout (v${v ?? '?'})`); -} else warn('../prettier checkout missing — the corpus prettier suites + layout-reference reading need it'); +} else warn( + '../prettier checkout missing — the corpus prettier suites + layout-reference reading need it' +); if (exists('../test262/test')) ok('../test262 checkout (conformance:test262 release gate)'); -else warn('../test262 checkout missing — conformance:test262 (the release gate, publish Step 3b) + bench:harvest:test262 need it'); +else warn( + '../test262 checkout missing — conformance:test262 (the release gate, publish Step 3b) + bench:harvest:test262 need it' +); if (exists('../wpt/css')) ok('../wpt/css checkout (sparse)'); else warn('../wpt/css checkout missing — bench:harvest:wpt unavailable (manual-cadence tool)'); @@ -192,15 +205,18 @@ try { if (missing.length > 0) { warn( `${view} view: ${missing.length}/${total} entries missing — ` + - `corpus:compare/bench on this view fail fast:\n ${missing.join('\n ')}`, + `corpus:compare/bench on this view fail fast:\n ${missing.join('\n ')}` ); - } else ok(`${view} view: all ${total - optional_missing.length}/${total} required entries present`); - for (const o of optional_missing) info(`${view} view: optional entry absent (fail-open, disclosed): ${o}`); + } else ok( + `${view} view: all ${total - optional_missing.length}/${total} required entries present` + ); + for (const o of optional_missing) + info(`${view} view: optional entry absent (fail-open, disclosed): ${o}`); } } catch (e) { warn( `cannot load the corpus entry list (${e instanceof Error ? e.message.split('\n')[0] : e}) — ` + - 'usually node_modules missing; run deno task bench:install', + 'usually node_modules missing; run deno task bench:install' ); } @@ -215,28 +231,36 @@ const artifacts: [string, string][] = [ [`target/release/${napi_lib}`, 'deno task build:napi (bench:node/bun)'], ['crates/tsv_wasm/pkg/all/deno', 'deno task build:wasm:all:deno (bench:deno)'], ['crates/tsv_wasm/pkg/all/nodejs', 'deno task build:wasm:all:nodejs (bench:node/bun)'], - ['crates/tsv_wasm/pkg/all/npm', 'deno task build:npm:all (publish)'], + ['crates/tsv_wasm/pkg/all/npm', 'deno task build:npm:all (publish)'] ]; for (const [path, task] of artifacts) { if (exists(path)) ok(path); else info(`${path} absent — ${task}`); } -info('freshness (artifact vs source mtimes) is enforced at run time by lib/check_artifact_freshness.ts'); +info( + 'freshness (artifact vs source mtimes) is enforced at run time by lib/check_artifact_freshness.ts' +); info('Deno sidecar (fixtures/compare tooling): verify with `cargo run -p tsv_debug check`'); // --- Verdict -------------------------------------------------------------------- console.log(''); if (errors > 0) { - console.log(`✗ doctor: ${errors} error(s), ${warnings} warning(s) — the ✗ items above would mislead a run; fix them first.`); + console.log( + `✗ doctor: ${errors} error(s), ${warnings} warning(s) — the ✗ items above would mislead a run; fix them first.` + ); Deno.exit(1); } if (warnings > 0) { if (strict) { - console.log(`✗ doctor --strict: ${warnings} warning(s) — this machine is not fully provisioned.`); + console.log( + `✗ doctor --strict: ${warnings} warning(s) — this machine is not fully provisioned.` + ); Deno.exit(1); } - console.log(`⚠ doctor: ${warnings} warning(s) — nothing misleading; the ⚠ tools fail loud on their own if used.`); + console.log( + `⚠ doctor: ${warnings} warning(s) — nothing misleading; the ⚠ tools fail loud on their own if used.` + ); } else { console.log('✓ doctor: everything present and consistent.'); } diff --git a/scripts/patch_npm_package.ts b/scripts/patch_npm_package.ts index 653e88af0..48d93f1be 100644 --- a/scripts/patch_npm_package.ts +++ b/scripts/patch_npm_package.ts @@ -47,7 +47,7 @@ if (variant !== 'format' && variant !== 'parse' && variant !== 'all') { const PKG_NAMES = { format: '@fuzdev/tsv_format_wasm', parse: '@fuzdev/tsv_parse_wasm', - all: '@fuzdev/tsv_wasm', + all: '@fuzdev/tsv_wasm' } as const; const pkg_name = PKG_NAMES[variant]; const has_format_exports = variant !== 'parse'; @@ -93,7 +93,7 @@ if (has_format_exports) { } if (has_parse_exports && !has_parse) { console.error( - `FAIL: ${variant} variant has no parse_* exports — was \`--features parse\` passed?`, + `FAIL: ${variant} variant has no parse_* exports — was \`--features parse\` passed?` ); Deno.exit(1); } @@ -114,7 +114,9 @@ if (has_format_exports) { Deno.exit(1); } } else if (classes.length) { - console.error(`FAIL: ${variant} variant contains class exports (${classes.join(', ')}) — stale build dir?`); + console.error( + `FAIL: ${variant} variant contains class exports (${classes.join(', ')}) — stale build dir?` + ); Deno.exit(1); } console.log(`Exports: ${[...fns, ...classes].join(', ')}`); @@ -174,17 +176,15 @@ export function init_sync(...args) { _ready = true; } -${ - fns - .map( - (f) => - `export function ${f}(source) { +${fns + .map( + (f) => + `export function ${f}(source) { _check(); return _${f}(source); -}`, - ) - .join('\n\n') -} +}` + ) + .join('\n\n')} `; Deno.writeTextFileSync(`${pkg_root}/browser.js`, browser_js); @@ -251,7 +251,7 @@ pkg.name = pkg_name; pkg.description = { format: 'formatter for Svelte, TypeScript, and CSS', parse: 'parser for Svelte, TypeScript, and CSS', - all: 'formatter and parser for Svelte, TypeScript, and CSS', + all: 'formatter and parser for Svelte, TypeScript, and CSS' }[variant]; pkg.type = 'module'; pkg.exports = { @@ -259,8 +259,8 @@ pkg.exports = { '.': { types: './index.d.ts', node: './index.js', - default: './browser.js', - }, + default: './browser.js' + } }; if (variant === 'all') { pkg.bin = { tsv: `./${cli_file}` }; @@ -275,7 +275,7 @@ pkg.files = [ ...(has_parse_exports ? ['tsv_ast.d.ts', locations_file, locations_dts] : []), ...(variant === 'all' ? [cli_file] : []), 'README.md', - 'LICENSE', + 'LICENSE' ]; pkg.keywords = [ 'typescript', @@ -285,17 +285,17 @@ pkg.keywords = [ ...(has_parse_exports ? ['parser', 'ast', 'acorn'] : []), ...(variant === 'all' ? ['cli'] : []), 'wasm', - 'webassembly', + 'webassembly' ]; pkg.homepage = 'https://github.com/fuzdev/tsv'; pkg.author = { name: 'Ryan Atkinson', email: 'mail@ryanatkn.com', - url: 'https://www.ryanatkn.com/', + url: 'https://www.ryanatkn.com/' }; pkg.repository = { type: 'git', - url: 'git+https://github.com/fuzdev/tsv.git', + url: 'git+https://github.com/fuzdev/tsv.git' }; pkg.bugs = 'https://github.com/fuzdev/tsv/issues'; pkg.funding = 'https://www.ryanatkn.com/funding'; @@ -336,9 +336,8 @@ async function print_summary(dir: string, files: string[]): Promise { for (const e of entries) { const name = e.name.padEnd(name_width); const size = format_size(e.size).padStart(size_width); - const annotation = e === wasm && wasm_gzipped !== null - ? ` → ${format_size(wasm_gzipped)} gzipped` - : ''; + const annotation = + e === wasm && wasm_gzipped !== null ? ` → ${format_size(wasm_gzipped)} gzipped` : ''; console.log(` ${name} ${size}${annotation}`); } } diff --git a/scripts/publish.ts b/scripts/publish.ts index 1979ff208..88b3601ce 100644 --- a/scripts/publish.ts +++ b/scripts/publish.ts @@ -48,7 +48,7 @@ const unknown_args = Deno.args.filter((arg, i) => if (unknown_args.length > 0) { console.error(`FAIL: unknown argument(s): ${unknown_args.join(' ')}`); console.error( - 'Usage: deno task publish [--wetrun] [--bump patch|minor|major] [--no-check] [--no-git]', + 'Usage: deno task publish [--wetrun] [--bump patch|minor|major] [--no-check] [--no-git]' ); Deno.exit(1); } @@ -87,16 +87,16 @@ const dec = new TextDecoder(); const packages = [ { label: '@fuzdev/tsv_format_wasm', - dir: 'crates/tsv_wasm/pkg/format/npm', + dir: 'crates/tsv_wasm/pkg/format/npm' }, { label: '@fuzdev/tsv_parse_wasm', - dir: 'crates/tsv_wasm/pkg/parse/npm', + dir: 'crates/tsv_wasm/pkg/parse/npm' }, { label: '@fuzdev/tsv_wasm', - dir: 'crates/tsv_wasm/pkg/all/npm', - }, + dir: 'crates/tsv_wasm/pkg/all/npm' + } ]; /** Only the release files — a stray file generated mid-pipeline must never @@ -155,7 +155,7 @@ if (branch === 'main') { if (behind > 0) { if (wetrun) { console.error( - ` FAIL: main is ${behind} commit(s) behind origin/main — run \`git pull\` first`, + ` FAIL: main is ${behind} commit(s) behind origin/main — run \`git pull\` first` ); Deno.exit(1); } @@ -229,13 +229,13 @@ if (wetrun) { const content = changelog_unreleased_content(); if (content === null) { console.error( - ` FAIL: no "## Unreleased" section in ${CHANGELOG_PATH} — add release notes before publishing`, + ` FAIL: no "## Unreleased" section in ${CHANGELOG_PATH} — add release notes before publishing` ); Deno.exit(1); } if (content === '') { console.error( - ` FAIL: "## Unreleased" section in ${CHANGELOG_PATH} is empty — add release notes before publishing`, + ` FAIL: "## Unreleased" section in ${CHANGELOG_PATH} is empty — add release notes before publishing` ); Deno.exit(1); } @@ -270,23 +270,25 @@ if (wetrun) { // Mirror the wetrun's requirements without exiting — report what it would do. const content = changelog_unreleased_content(); if (content === null) { - console.warn(` WARN: no "## Unreleased" section in ${CHANGELOG_PATH} — a fresh wetrun would FAIL`); + console.warn( + ` WARN: no "## Unreleased" section in ${CHANGELOG_PATH} — a fresh wetrun would FAIL` + ); } else if (content === '') { console.warn(` WARN: "## Unreleased" section is empty — a fresh wetrun would FAIL`); } if (!bump) { console.warn( - ' WARN: no --bump given — a fresh wetrun would FAIL (required, and must match the CHANGELOG marker)', + ' WARN: no --bump given — a fresh wetrun would FAIL (required, and must match the CHANGELOG marker)' ); } if (!declared) { console.warn( - ` WARN: no "" marker in ${CHANGELOG_PATH} — a fresh wetrun would FAIL`, + ` WARN: no "" marker in ${CHANGELOG_PATH} — a fresh wetrun would FAIL` ); } if (bump && declared && bump !== declared) { console.warn( - ` WARN: --bump ${bump} disagrees with CHANGELOG marker — a fresh wetrun would FAIL`, + ` WARN: --bump ${bump} disagrees with CHANGELOG marker — a fresh wetrun would FAIL` ); } if (bump && declared && bump === declared) { @@ -341,19 +343,19 @@ if (no_check) { exists('../acorn-typescript/test') ? null : '../acorn-typescript checkout', exists('../typescript/tests') ? null : '../typescript checkout', exists('../test262/test') ? null : '../test262 checkout', - exists('benches/js/node_modules') ? null : 'benches/js/node_modules (deno task bench:install)', + exists('benches/js/node_modules') ? null : 'benches/js/node_modules (deno task bench:install)' ].filter((m): m is string => m !== null); if (missing.length > 0 && wetrun) { console.error( ` FAIL: missing ${missing.join(' + ')} — the conformance gates cannot run. ` + - 'Clone/install the missing pieces, or pass --no-check to release without gates (explicitly).', + 'Clone/install the missing pieces, or pass --no-check to release without gates (explicitly).' ); Deno.exit(1); } else if (missing.length > 0) { conformance_skip_reason = `missing ${missing.join(' + ')}`; console.warn( ` WARN: skipping — ${conformance_skip_reason}. ` + - 'A --wetrun would FAIL here; run `deno task conformance:all` on a machine with the oracles.', + 'A --wetrun would FAIL here; run `deno task conformance:all` on a machine with the oracles.' ); } else { run( @@ -365,7 +367,7 @@ if (no_check) { ' `deno task corpus:compare:format ~/dev/` on the single repo to rule out\n' + ' the known --all FFI heisenbug (benches/js/CLAUDE.md §Known Issues) before treating\n' + ' it as a real regression. A conformance:test262 GATE FAIL is a real positive-parse\n' + - ' regression (or a deliberate test262 pull needing a POSITIVE_PASSED_PIN re-pin).', + ' regression (or a deliberate test262 pull needing a POSITIVE_PASSED_PIN re-pin).' ); } } @@ -392,13 +394,13 @@ if (no_check) { if (wetrun) { console.error( ` FAIL: ${audit_corpus_skip_reason} — the corpus audit cannot run over real code. ` + - 'Clone it, or pass --no-check to release without the audit (explicitly).', + 'Clone it, or pass --no-check to release without the audit (explicitly).' ); Deno.exit(1); } console.warn( ` WARN: skipping — ${audit_corpus_skip_reason}. ` + - 'A --wetrun would FAIL here; run `deno task audit:corpus` on a machine with the corpus.', + 'A --wetrun would FAIL here; run `deno task audit:corpus` on a machine with the corpus.' ); } else { run( @@ -408,7 +410,7 @@ if (no_check) { undefined, ' Corpus robustness audit found a content-loss / panic / non-idempotency regression over\n' + ' REAL code (what `deno task check`’s fixture-only gate cannot see). Triage the failing\n' + - ' leg by name with the matching `tsv_debug` audit on the single repo it points at.', + ' leg by name with the matching `tsv_debug` audit on the single repo it points at.' ); } } @@ -455,7 +457,7 @@ for (const { label, dir } of packages) { env: { PKG_DIR: dir }, stdin: 'inherit', stdout: 'inherit', - stderr: 'inherit', + stderr: 'inherit' }).outputSync(); if (!result.success) { console.error(`\n FAIL: artifact tests for ${label}`); @@ -479,10 +481,9 @@ for (let i = 0; i < packages.length; i++) { } console.log(` Publishing ${label}...`); const not_published = packages.slice(i); - const fail_hint = - ` Packages not published — re-run \`deno task publish --wetrun\` to retry, or publish manually:\n${ - not_published.map((p) => ` cd ${p.dir} && npm publish --access public`).join('\n') - }`; + const fail_hint = ` Packages not published — re-run \`deno task publish --wetrun\` to retry, or publish manually:\n${not_published + .map((p) => ` cd ${p.dir} && npm publish --access public`) + .join('\n')}`; run(`npm publish ${label}`, 'npm', ['publish', '--access', 'public'], dir, fail_hint); console.log(` Published ${label}@${version}`); } else { @@ -498,7 +499,7 @@ for (let i = 0; i < packages.length; i++) { const preview = (bump as BumpLevel | null) ?? changelog_declared_bump(); const target = preview ? `v${bump_version(version, preview)}` : 'the bumped version'; console.log( - ` PASS: ${label} packs cleanly (v${version} already published — a real run publishes ${target})`, + ` PASS: ${label} packs cleanly (v${version} already published — a real run publishes ${target})` ); } else { if (res.stdout) console.error(res.stdout); @@ -556,7 +557,7 @@ for (const { label, dir } of packages) { console.log( wetrun ? ` Published ${label}@${version} — ${size_note}` - : ` ${label}@${version} — ${size_note}`, + : ` ${label}@${version} — ${size_note}` ); } if (conformance_skip_reason !== null) { @@ -584,18 +585,18 @@ console.log(''); function capture( cmd: string, args: string[], - cwd?: string, + cwd?: string ): { success: boolean; stdout: string; stderr: string } { const result = new Deno.Command(cmd, { args, cwd, stdout: 'piped', - stderr: 'piped', + stderr: 'piped' }).outputSync(); return { success: result.success, stdout: dec.decode(result.stdout).trim(), - stderr: dec.decode(result.stderr).trim(), + stderr: dec.decode(result.stderr).trim() }; } @@ -615,7 +616,7 @@ function run(label: string, cmd: string, args: string[], cwd?: string, fail_hint cwd, stdin: 'inherit', stdout: 'inherit', - stderr: 'inherit', + stderr: 'inherit' }).outputSync(); if (!result.success) { console.error(`\n FAIL: ${label} (exit code ${result.code})`); @@ -662,14 +663,14 @@ function stamp_changelog(new_version: string): void { const stamped = changelog.replace(with_marker, `${fresh}## ${new_version}\n`); Deno.writeTextFileSync(CHANGELOG_PATH, stamped); console.log( - ` Stamped ${CHANGELOG_PATH}: ## Unreleased -> ## ${new_version}; seeded fresh ## Unreleased (bump: patch)`, + ` Stamped ${CHANGELOG_PATH}: ## Unreleased -> ## ${new_version}; seeded fresh ## Unreleased (bump: patch)` ); } else if (/^## Unreleased$/m.test(changelog)) { // No marker (defensive — a wetrun would have failed earlier). Rename + seed. const stamped = changelog.replace(/^## Unreleased$/m, `${fresh}## ${new_version}`); Deno.writeTextFileSync(CHANGELOG_PATH, stamped); console.log( - ` Stamped ${CHANGELOG_PATH}: ## Unreleased -> ## ${new_version}; seeded fresh ## Unreleased (bump: patch)`, + ` Stamped ${CHANGELOG_PATH}: ## Unreleased -> ## ${new_version}; seeded fresh ## Unreleased (bump: patch)` ); } else { console.warn(` WARN: no "## Unreleased" section in ${CHANGELOG_PATH} — nothing to stamp`); @@ -723,18 +724,20 @@ function changelog_declared_bump(): BumpLevel | null { * and they must match. Exits if either is missing or they disagree. */ function resolve_bump(flag: BumpLevel | null, declared: BumpLevel | null): BumpLevel { if (!flag) { - console.error(' FAIL: --bump patch|minor|major is required (and must match the CHANGELOG marker)'); + console.error( + ' FAIL: --bump patch|minor|major is required (and must match the CHANGELOG marker)' + ); Deno.exit(1); } if (!declared) { console.error( - ` FAIL: no "" marker under ## Unreleased in ${CHANGELOG_PATH} — declare the bump there too`, + ` FAIL: no "" marker under ## Unreleased in ${CHANGELOG_PATH} — declare the bump there too` ); Deno.exit(1); } if (flag !== declared) { console.error( - ` FAIL: --bump ${flag} disagrees with CHANGELOG marker (they must match)`, + ` FAIL: --bump ${flag} disagrees with CHANGELOG marker (they must match)` ); Deno.exit(1); } diff --git a/scripts/run_if_stale.ts b/scripts/run_if_stale.ts index f1b2c2d04..6bd06d3d6 100644 --- a/scripts/run_if_stale.ts +++ b/scripts/run_if_stale.ts @@ -41,7 +41,7 @@ import { CORE_CRATES, newest_source_mtime, type SourceMtime, - WASM_CRATES, + WASM_CRATES } from '../benches/js/lib/check_artifact_freshness.ts'; const ROOT = fileURLToPath(new URL('..', import.meta.url)); @@ -93,7 +93,7 @@ const main = async (): Promise => { // Strict `<` so an artifact built in the same second as its source rebuilds. if (source.ms < target_ms) { console.log( - `run_if_stale: ${target} is fresh — skipping \`${command[0]}\` (TSV_BUILD_FORCE=1 to force)`, + `run_if_stale: ${target} is fresh — skipping \`${command[0]}\` (TSV_BUILD_FORCE=1 to force)` ); return; } @@ -104,7 +104,7 @@ const main = async (): Promise => { args: command.slice(1), stdin: 'inherit', stdout: 'inherit', - stderr: 'inherit', + stderr: 'inherit' }).outputSync(); if (!result.success) Deno.exit(result.code); }; diff --git a/scripts/size.ts b/scripts/size.ts index ce78138a1..a2ef45af2 100644 --- a/scripts/size.ts +++ b/scripts/size.ts @@ -18,7 +18,7 @@ export function format_size(bytes: number): string { export async function gzip_size(path: string): Promise { const output = await new Deno.Command('gzip', { args: ['-c', path], - stdout: 'piped', + stdout: 'piped' }).output(); return output.stdout.length; } diff --git a/scripts/test_npm.ts b/scripts/test_npm.ts index 81dd91d4a..7cedb4c82 100644 --- a/scripts/test_npm.ts +++ b/scripts/test_npm.ts @@ -31,7 +31,7 @@ import { readdirSync, readFileSync, rmSync, - writeFileSync, + writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; @@ -44,10 +44,7 @@ if (!pkg_dir) { } const variant = /\/(format|parse|all)\/npm\/?$/.exec(pkg_dir)?.[1] as - | 'format' - | 'parse' - | 'all' - | undefined; + 'format' | 'parse' | 'all' | undefined; if (!variant) { console.error(`PKG_DIR must end in /(format|parse|all)/npm — got ${pkg_dir}`); process.exit(1); @@ -57,7 +54,7 @@ const has_parse = variant !== 'format'; const PKG_NAMES = { format: '@fuzdev/tsv_format_wasm', parse: '@fuzdev/tsv_parse_wasm', - all: '@fuzdev/tsv_wasm', + all: '@fuzdev/tsv_wasm' }; const node_entry = await import(`../${pkg_dir}/index.js`); @@ -71,7 +68,7 @@ const deep_equal_json = (a: unknown, b: unknown): boolean => describe(`package metadata: ${pkg_dir}`, () => { const pkg = JSON.parse( - readFileSync(new URL(`../${pkg_dir}/package.json`, import.meta.url), 'utf-8'), + readFileSync(new URL(`../${pkg_dir}/package.json`, import.meta.url), 'utf-8') ); it('has the right name', () => { @@ -85,7 +82,7 @@ describe(`package metadata: ${pkg_dir}`, () => { assert.ok(rel, `exports['.'].${key} missing`); assert.ok( existsSync(new URL(`../${pkg_dir}/${rel}`, import.meta.url)), - `exports['.'].${key} → ${rel} does not exist`, + `exports['.'].${key} → ${rel} does not exist` ); } }); @@ -94,7 +91,7 @@ describe(`package metadata: ${pkg_dir}`, () => { for (const rel of pkg.files) { assert.ok( existsSync(new URL(`../${pkg_dir}/${rel}`, import.meta.url)), - `files entry ${rel} does not exist`, + `files entry ${rel} does not exist` ); } }); @@ -178,15 +175,15 @@ describe(`node entry (index.js): ${pkg_dir}`, () => { // parse_X is defined as JSON.parse(parse_X_json(src)); the two must agree. assert.deepEqual( JSON.parse(node_entry.parse_typescript_json('const x = 1;')), - node_entry.parse_typescript('const x = 1;'), + node_entry.parse_typescript('const x = 1;') ); assert.deepEqual( JSON.parse(node_entry.parse_svelte_json('

x
')), - node_entry.parse_svelte('
x
'), + node_entry.parse_svelte('
x
') ); assert.deepEqual( JSON.parse(node_entry.parse_css_json('a { color: red }')), - node_entry.parse_css('a { color: red }'), + node_entry.parse_css('a { color: red }') ); }); }); @@ -199,7 +196,10 @@ describe(`locations helper (index.js): ${pkg_dir}`, { skip: !has_parse }, () => it('reconstruct_locations is EXACT for TypeScript (equals the full wire)', () => { const ts = 'const x = 1;\nconst y = 2;\n'; const full = node_entry.parse_typescript(ts); - const recon = node_entry.reconstruct_locations(node_entry.parse_typescript_no_locations(ts), ts); + const recon = node_entry.reconstruct_locations( + node_entry.parse_typescript_no_locations(ts), + ts + ); // The span-only wire is the full wire minus `loc`; adding it back must // reproduce acorn's `loc` byte-for-byte on every node. assert.deepEqual(recon, full); @@ -388,7 +388,7 @@ describe(`browser entry (browser.js): ${pkg_dir}`, () => { const out = browser.reconstruct_locations(ast, 'x = 1'); assert.deepEqual(out.loc, { start: { line: 1, column: 0 }, - end: { line: 1, column: 5 }, + end: { line: 1, column: 5 } }); }); @@ -426,7 +426,7 @@ describe(`cli (cli.js): ${pkg_dir}`, { skip: variant !== 'all' }, () => { spawnSync(process.execPath, [cli_path, ...args], { encoding: 'utf-8', input: stdin, - cwd, + cwd }); it('format --content prints formatted source', () => { @@ -485,13 +485,27 @@ describe(`cli (cli.js): ${pkg_dir}`, { skip: variant !== 'all' }, () => { it('parse --goal script accepts `await` as an identifier; module/default reject it', () => { const script = run_cli([ - 'parse', '--content', 'var await = 1;', '--parser', 'ts', '--goal', 'script', + 'parse', + '--content', + 'var await = 1;', + '--parser', + 'ts', + '--goal', + 'script' ]); assert.equal(script.status, 0, script.stderr); assert.match(script.stdout, /"sourceType":"script"/); // the same source is reserved at Module goal (explicit and default) - const mod = run_cli(['parse', '--content', 'var await = 1;', '--parser', 'ts', '--goal', 'module']); + const mod = run_cli([ + 'parse', + '--content', + 'var await = 1;', + '--parser', + 'ts', + '--goal', + 'module' + ]); assert.equal(mod.status, 1); const dflt = run_cli(['parse', '--content', 'var await = 1;', '--parser', 'ts']); assert.equal(dflt.status, 1); @@ -499,7 +513,13 @@ describe(`cli (cli.js): ${pkg_dir}`, { skip: variant !== 'all' }, () => { it('format --goal script formats an `await` arrow param', () => { const result = run_cli([ - 'format', '--content', 'await => 1;', '--parser', 'ts', '--goal', 'script', + 'format', + '--content', + 'await => 1;', + '--parser', + 'ts', + '--goal', + 'script' ]); assert.equal(result.status, 0, result.stderr); assert.equal(result.stdout, '(await) => 1;\n'); @@ -538,10 +558,10 @@ describe(`cli (cli.js): ${pkg_dir}`, { skip: variant !== 'all' }, () => { const result = run_cli(['format', dir]); assert.equal(result.status, 0); - assert.deepEqual( - result.stdout.trim().split('\n').sort(), - [join(dir, 'a.ts'), join(dir, 'nested', 'b.svelte')], - ); + assert.deepEqual(result.stdout.trim().split('\n').sort(), [ + join(dir, 'a.ts'), + join(dir, 'nested', 'b.svelte') + ]); assert.match(result.stderr, /2 formatted, 1 unchanged/); assert.equal(readFileSync(join(dir, 'a.ts'), 'utf-8'), 'const x = 1;\n'); assert.equal(readFileSync(join(dir, 'nested', 'b.svelte'), 'utf-8'), '
x
\n'); @@ -637,7 +657,10 @@ describe(`cli (cli.js): ${pkg_dir}`, { skip: variant !== 'all' }, () => { // (a) cd into proj and list '.'; (b) from base, list proj by path const inside = run_cli(['format', '--list', '.'], undefined, proj); const outside = run_cli(['format', '--list', proj], undefined, base); - for (const [label, out] of [['inside', inside], ['outside', outside]] as const) { + for (const [label, out] of [ + ['inside', inside], + ['outside', outside] + ] as const) { assert.equal(out.status, 0, `${label}: ${out.stderr}`); assert.match(out.stdout, /src\.ts/, label); assert.doesNotMatch(out.stdout, /out\.ts/, `${label}: gen/ honored regardless of cwd`); @@ -698,7 +721,7 @@ describe(`cli (cli.js): ${pkg_dir}`, { skip: variant !== 'all' }, () => { '--content', 'const x = 1;', '--parser', - 'ts', + 'ts' ]); assert.equal(result.status, 2); assert.match(result.stderr, /--jobs applies to file paths/); @@ -761,14 +784,7 @@ describe(`cli (cli.js): ${pkg_dir}`, { skip: variant !== 'all' }, () => { }); it('parse --pretty prints tab-indented JSON', () => { - const result = run_cli([ - 'parse', - '--pretty', - '--content', - 'const x = 1;', - '--parser', - 'ts', - ]); + const result = run_cli(['parse', '--pretty', '--content', 'const x = 1;', '--parser', 'ts']); assert.equal(result.status, 0); assert.match(result.stdout, /^\{\n\t"type": "Program",\n/); }); @@ -839,7 +855,7 @@ describe(`cli (cli.js): ${pkg_dir}`, { skip: variant !== 'all' }, () => { describe(`discovery parity (cli.js): ${pkg_dir}`, { skip: variant !== 'all' }, () => { const cli_path = new URL(`../${pkg_dir}/cli.js`, import.meta.url).pathname; const table = JSON.parse( - readFileSync(new URL('../tests/discovery/scenarios.json', import.meta.url), 'utf-8'), + readFileSync(new URL('../tests/discovery/scenarios.json', import.meta.url), 'utf-8') ); /** Materialize a scenario `tree`: string = file (parents created), null = empty dir. */ @@ -864,7 +880,7 @@ describe(`discovery parity (cli.js): ${pkg_dir}`, { skip: variant !== 'all' }, ( for (const { target, expected } of scenario.cases) { const arg = target === '' ? root : join(root, target); const result = spawnSync(process.execPath, [cli_path, 'format', '--list', arg], { - encoding: 'utf-8', + encoding: 'utf-8' }); assert.equal(result.status, 0, `${scenario.name} [${target}]: ${result.stderr}`); const actual = result.stdout diff --git a/scripts/validate_artifacts.ts b/scripts/validate_artifacts.ts index 9640b567f..3035e71f9 100644 --- a/scripts/validate_artifacts.ts +++ b/scripts/validate_artifacts.ts @@ -65,7 +65,7 @@ const TARGETS = ['npm', 'deno'] as const; const BOUNDS = { format: { min: 2_005_000, max: 2_350_000 }, parse: { min: 934_000, max: 1_097_000 }, - all: { min: 2_210_000, max: 2_595_000 }, + all: { min: 2_210_000, max: 2_595_000 } }; // all = format + parse. `all − format` is the parse feature (parser convert @@ -76,7 +76,7 @@ const BOUNDS = { // gate-health signal). A delta near zero means a feature gate broke. const DELTAS = { format: { min: 209_000, max: 245_000 }, // all − format - parse: { min: 1_295_000, max: 1_525_000 }, // all − parse + parse: { min: 1_295_000, max: 1_525_000 } // all − parse }; console.log('=== WASM binary sizes ==='); @@ -96,11 +96,11 @@ for (const target of TARGETS) { const { min, max } = BOUNDS[variant]; if (size < min) { fail( - `${label}: ${format_size(size)} (${size} B) < min ${format_size(min)} — suspiciously small`, + `${label}: ${format_size(size)} (${size} B) < min ${format_size(min)} — suspiciously small` ); } else if (size > max) { fail( - `${label}: ${format_size(size)} (${size} B) > max ${format_size(max)} — size regression`, + `${label}: ${format_size(size)} (${size} B) > max ${format_size(max)} — size regression` ); } else { pass(`${label}: ${format_size(size)} (${size} B)`); @@ -120,15 +120,15 @@ for (const target of TARGETS) { const delta = all_bytes - subset_bytes; if (delta < min) { fail( - `all - ${variant} (${target}) = ${format_size(delta)} — expected ≥${ - format_size(min) - } (feature gate broken?)`, + `all - ${variant} (${target}) = ${format_size(delta)} — expected ≥${format_size( + min + )} (feature gate broken?)` ); } else if (delta > max) { fail( - `all - ${variant} (${target}) = ${format_size(delta)} — expected ≤${ - format_size(max) - } (unexpected bloat)`, + `all - ${variant} (${target}) = ${format_size(delta)} — expected ≤${format_size( + max + )} (unexpected bloat)` ); } else { pass(`all - ${variant} (${target}) = ${format_size(delta)}`); @@ -156,7 +156,7 @@ const smoke_entries = (variant: 'format' | 'parse' | 'all'): SmokeTarget[] => [ entry: `crates/tsv_wasm/pkg/${variant}/npm/index.js`, has_format: variant !== 'parse', has_parse: variant !== 'format', - is_npm: true, + is_npm: true }, // deno-target bundle (auto-init at import) { @@ -164,8 +164,8 @@ const smoke_entries = (variant: 'format' | 'parse' | 'all'): SmokeTarget[] => [ entry: `crates/tsv_wasm/pkg/${variant}/deno/tsv_wasm.js`, has_format: variant !== 'parse', has_parse: variant !== 'format', - is_npm: false, - }, + is_npm: false + } ]; const smoke_targets: SmokeTarget[] = VARIANTS.flatMap(smoke_entries); @@ -189,13 +189,13 @@ for (const { label, entry, has_format, has_parse, is_npm } of smoke_targets) { check( label, 'format_typescript', - () => mod.format_typescript('const x=1') === 'const x = 1;\n', + () => mod.format_typescript('const x=1') === 'const x = 1;\n' ); check(label, 'format_css', () => mod.format_css('a{color:red}') === 'a {\n\tcolor: red;\n}\n'); check( label, 'format_svelte', - () => mod.format_svelte('
x
') === '
x
\n', + () => mod.format_svelte('
x
') === '
x
\n' ); check(label, 'IgnoreStack', () => { const ctor = (mod as Record).IgnoreStack as @@ -230,24 +230,24 @@ for (const { label, entry, has_format, has_parse, is_npm } of smoke_targets) { check( label, 'IgnoreStack absent (parse-only build)', - () => (mod as Record).IgnoreStack === undefined, + () => (mod as Record).IgnoreStack === undefined ); } if (has_parse) { check( label, 'parse_typescript', - () => (mod.parse_typescript('const x = 1;') as { type: string }).type === 'Program', + () => (mod.parse_typescript('const x = 1;') as { type: string }).type === 'Program' ); check( label, 'parse_svelte', - () => (mod.parse_svelte('
x
') as { type: string }).type === 'Root', + () => (mod.parse_svelte('
x
') as { type: string }).type === 'Root' ); check( label, 'parse_css', - () => (mod.parse_css('a { color: red }') as { type: string }).type === 'StyleSheetFile', + () => (mod.parse_css('a { color: red }') as { type: string }).type === 'StyleSheetFile' ); } // The pure-JS `no-locations` reconstruction helper is patched into the npm @@ -258,8 +258,7 @@ for (const { label, entry, has_format, has_parse, is_npm } of smoke_targets) { if (has_parse) { check(label, 'reconstruct_locations', () => { const reconstruct = (mod as Record).reconstruct_locations as - | ((ast: unknown, source: string) => { loc?: unknown }) - | undefined; + ((ast: unknown, source: string) => { loc?: unknown }) | undefined; if (typeof reconstruct !== 'function') return false; const ast = mod.parse_typescript_no_locations('const x = 1;') as { loc?: unknown }; const out = reconstruct(ast, 'const x = 1;'); @@ -269,7 +268,7 @@ for (const { label, entry, has_format, has_parse, is_npm } of smoke_targets) { check( label, 'reconstruct_locations absent (format-only build)', - () => (mod as Record).reconstruct_locations === undefined, + () => (mod as Record).reconstruct_locations === undefined ); } } @@ -290,12 +289,12 @@ function check(target: string, name: string, assertion: () => boolean): void { // --- Summary --- console.log( - `\n=== Artifact validation: ${passed} passed, ${failed} failed, ${skipped} skipped ===`, + `\n=== Artifact validation: ${passed} passed, ${failed} failed, ${skipped} skipped ===` ); if (failed > 0) Deno.exit(1); if (passed === 0) { console.error( - 'FAIL: no artifacts found to validate — run `deno task build:npm:format` etc. first', + 'FAIL: no artifacts found to validate — run `deno task build:npm:format` etc. first' ); Deno.exit(1); } diff --git a/tests/fixtures/typescript/expressions/arrow/return_type_ternary_colon/expected.json b/tests/fixtures/typescript/expressions/arrow/return_type_ternary_colon/expected.json new file mode 100644 index 000000000..9d1e87386 --- /dev/null +++ b/tests/fixtures/typescript/expressions/arrow/return_type_ternary_colon/expected.json @@ -0,0 +1,1503 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 677, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " The `:` closes the ternary — the arrow after it is the alternate, not this paren's body", + "start": 20, + "end": 110, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 91 + } + } + }, + { + "type": "Line", + "value": " The arrow sits past a second `?`, in the nested ternary's alternate", + "start": 148, + "end": 218, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 71 + } + } + }, + { + "type": "Line", + "value": " The parenthesized consequent itself opens with `(`", + "start": 271, + "end": 324, + "loc": { + "start": { + "line": 8, + "column": 1 + }, + "end": { + "line": 8, + "column": 54 + } + } + }, + { + "type": "Line", + "value": " The `:` does annotate a signature here — plain return type", + "start": 374, + "end": 435, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 11, + "column": 62 + } + } + }, + { + "type": "Line", + "value": " An annotated arrow as the ternary consequent", + "start": 464, + "end": 511, + "loc": { + "start": { + "line": 14, + "column": 1 + }, + "end": { + "line": 14, + "column": 48 + } + } + }, + { + "type": "Line", + "value": " Conditional type as the return type — its `?` and `:` belong to the type", + "start": 547, + "end": 622, + "loc": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 17, + "column": 76 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 676, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 667, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 19, + "column": 9 + } + }, + "body": [ + { + "type": "VariableDeclaration", + "start": 112, + "end": 145, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 34 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 118, + "end": 144, + "loc": { + "start": { + "line": 3, + "column": 7 + }, + "end": { + "line": 3, + "column": 33 + } + }, + "id": { + "type": "Identifier", + "start": 118, + "end": 120, + "loc": { + "start": { + "line": 3, + "column": 7 + }, + "end": { + "line": 3, + "column": 9 + } + }, + "name": "s1" + }, + "init": { + "type": "ConditionalExpression", + "start": 123, + "end": 144, + "loc": { + "start": { + "line": 3, + "column": 12 + }, + "end": { + "line": 3, + "column": 33 + } + }, + "test": { + "type": "Identifier", + "start": 123, + "end": 124, + "loc": { + "start": { + "line": 3, + "column": 12 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "name": "a" + }, + "consequent": { + "type": "SequenceExpression", + "start": 128, + "end": 132, + "loc": { + "start": { + "line": 3, + "column": 17 + }, + "end": { + "line": 3, + "column": 21 + } + }, + "expressions": [ + { + "type": "Identifier", + "start": 128, + "end": 129, + "loc": { + "start": { + "line": 3, + "column": 17 + }, + "end": { + "line": 3, + "column": 18 + } + }, + "name": "b" + }, + { + "type": "Identifier", + "start": 131, + "end": 132, + "loc": { + "start": { + "line": 3, + "column": 20 + }, + "end": { + "line": 3, + "column": 21 + } + }, + "name": "c" + } + ] + }, + "alternate": { + "type": "ArrowFunctionExpression", + "start": 136, + "end": 144, + "loc": { + "start": { + "line": 3, + "column": 25 + }, + "end": { + "line": 3, + "column": 33 + } + }, + "id": null, + "expression": true, + "generator": false, + "async": false, + "params": [ + { + "type": "Identifier", + "start": 137, + "end": 138, + "loc": { + "start": { + "line": 3, + "column": 26 + }, + "end": { + "line": 3, + "column": 27 + } + }, + "name": "p" + } + ], + "body": { + "type": "Literal", + "start": 143, + "end": 144, + "loc": { + "start": { + "line": 3, + "column": 32 + }, + "end": { + "line": 3, + "column": 33 + } + }, + "value": 1, + "raw": "1" + } + } + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " The `:` closes the ternary — the arrow after it is the alternate, not this paren's body", + "start": 20, + "end": 110 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 220, + "end": 268, + "loc": { + "start": { + "line": 6, + "column": 1 + }, + "end": { + "line": 6, + "column": 49 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 226, + "end": 267, + "loc": { + "start": { + "line": 6, + "column": 7 + }, + "end": { + "line": 6, + "column": 48 + } + }, + "id": { + "type": "Identifier", + "start": 226, + "end": 228, + "loc": { + "start": { + "line": 6, + "column": 7 + }, + "end": { + "line": 6, + "column": 9 + } + }, + "name": "s2" + }, + "init": { + "type": "ConditionalExpression", + "start": 231, + "end": 267, + "loc": { + "start": { + "line": 6, + "column": 12 + }, + "end": { + "line": 6, + "column": 48 + } + }, + "test": { + "type": "Identifier", + "start": 231, + "end": 232, + "loc": { + "start": { + "line": 6, + "column": 12 + }, + "end": { + "line": 6, + "column": 13 + } + }, + "name": "a" + }, + "consequent": { + "type": "LogicalExpression", + "start": 235, + "end": 248, + "loc": { + "start": { + "line": 6, + "column": 16 + }, + "end": { + "line": 6, + "column": 29 + } + }, + "left": { + "type": "Identifier", + "start": 235, + "end": 236, + "loc": { + "start": { + "line": 6, + "column": 16 + }, + "end": { + "line": 6, + "column": 17 + } + }, + "name": "b" + }, + "operator": "||", + "right": { + "type": "LogicalExpression", + "start": 241, + "end": 247, + "loc": { + "start": { + "line": 6, + "column": 22 + }, + "end": { + "line": 6, + "column": 28 + } + }, + "left": { + "type": "Identifier", + "start": 241, + "end": 242, + "loc": { + "start": { + "line": 6, + "column": 22 + }, + "end": { + "line": 6, + "column": 23 + } + }, + "name": "c" + }, + "operator": "??", + "right": { + "type": "Identifier", + "start": 246, + "end": 247, + "loc": { + "start": { + "line": 6, + "column": 27 + }, + "end": { + "line": 6, + "column": 28 + } + }, + "name": "d" + } + } + }, + "alternate": { + "type": "ConditionalExpression", + "start": 251, + "end": 267, + "loc": { + "start": { + "line": 6, + "column": 32 + }, + "end": { + "line": 6, + "column": 48 + } + }, + "test": { + "type": "Identifier", + "start": 251, + "end": 252, + "loc": { + "start": { + "line": 6, + "column": 32 + }, + "end": { + "line": 6, + "column": 33 + } + }, + "name": "e" + }, + "consequent": { + "type": "ArrowFunctionExpression", + "start": 255, + "end": 263, + "loc": { + "start": { + "line": 6, + "column": 36 + }, + "end": { + "line": 6, + "column": 44 + } + }, + "id": null, + "expression": true, + "generator": false, + "async": false, + "params": [ + { + "type": "Identifier", + "start": 256, + "end": 257, + "loc": { + "start": { + "line": 6, + "column": 37 + }, + "end": { + "line": 6, + "column": 38 + } + }, + "name": "p" + } + ], + "body": { + "type": "Literal", + "start": 262, + "end": 263, + "loc": { + "start": { + "line": 6, + "column": 43 + }, + "end": { + "line": 6, + "column": 44 + } + }, + "value": 1, + "raw": "1" + } + }, + "alternate": { + "type": "Identifier", + "start": 266, + "end": 267, + "loc": { + "start": { + "line": 6, + "column": 47 + }, + "end": { + "line": 6, + "column": 48 + } + }, + "name": "f" + } + } + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " The arrow sits past a second `?`, in the nested ternary's alternate", + "start": 148, + "end": 218 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 326, + "end": 371, + "loc": { + "start": { + "line": 9, + "column": 1 + }, + "end": { + "line": 9, + "column": 46 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 332, + "end": 370, + "loc": { + "start": { + "line": 9, + "column": 7 + }, + "end": { + "line": 9, + "column": 45 + } + }, + "id": { + "type": "Identifier", + "start": 332, + "end": 334, + "loc": { + "start": { + "line": 9, + "column": 7 + }, + "end": { + "line": 9, + "column": 9 + } + }, + "name": "s3" + }, + "init": { + "type": "ConditionalExpression", + "start": 337, + "end": 370, + "loc": { + "start": { + "line": 9, + "column": 12 + }, + "end": { + "line": 9, + "column": 45 + } + }, + "test": { + "type": "Identifier", + "start": 337, + "end": 338, + "loc": { + "start": { + "line": 9, + "column": 12 + }, + "end": { + "line": 9, + "column": 13 + } + }, + "name": "z" + }, + "consequent": { + "type": "LogicalExpression", + "start": 341, + "end": 359, + "loc": { + "start": { + "line": 9, + "column": 16 + }, + "end": { + "line": 9, + "column": 34 + } + }, + "left": { + "type": "Identifier", + "start": 341, + "end": 342, + "loc": { + "start": { + "line": 9, + "column": 16 + }, + "end": { + "line": 9, + "column": 17 + } + }, + "name": "x" + }, + "operator": "||", + "right": { + "type": "LogicalExpression", + "start": 347, + "end": 358, + "loc": { + "start": { + "line": 9, + "column": 22 + }, + "end": { + "line": 9, + "column": 33 + } + }, + "left": { + "type": "SequenceExpression", + "start": 348, + "end": 352, + "loc": { + "start": { + "line": 9, + "column": 23 + }, + "end": { + "line": 9, + "column": 27 + } + }, + "expressions": [ + { + "type": "Identifier", + "start": 348, + "end": 349, + "loc": { + "start": { + "line": 9, + "column": 23 + }, + "end": { + "line": 9, + "column": 24 + } + }, + "name": "a" + }, + { + "type": "Identifier", + "start": 351, + "end": 352, + "loc": { + "start": { + "line": 9, + "column": 26 + }, + "end": { + "line": 9, + "column": 27 + } + }, + "name": "b" + } + ] + }, + "operator": "??", + "right": { + "type": "Identifier", + "start": 357, + "end": 358, + "loc": { + "start": { + "line": 9, + "column": 32 + }, + "end": { + "line": 9, + "column": 33 + } + }, + "name": "c" + } + } + }, + "alternate": { + "type": "ArrowFunctionExpression", + "start": 362, + "end": 370, + "loc": { + "start": { + "line": 9, + "column": 37 + }, + "end": { + "line": 9, + "column": 45 + } + }, + "id": null, + "expression": true, + "generator": false, + "async": false, + "params": [ + { + "type": "Identifier", + "start": 363, + "end": 364, + "loc": { + "start": { + "line": 9, + "column": 38 + }, + "end": { + "line": 9, + "column": 39 + } + }, + "name": "p" + } + ], + "body": { + "type": "Literal", + "start": 369, + "end": 370, + "loc": { + "start": { + "line": 9, + "column": 44 + }, + "end": { + "line": 9, + "column": 45 + } + }, + "value": 1, + "raw": "1" + } + } + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " The parenthesized consequent itself opens with `(`", + "start": 271, + "end": 324 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 437, + "end": 461, + "loc": { + "start": { + "line": 12, + "column": 1 + }, + "end": { + "line": 12, + "column": 25 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 443, + "end": 460, + "loc": { + "start": { + "line": 12, + "column": 7 + }, + "end": { + "line": 12, + "column": 24 + } + }, + "id": { + "type": "Identifier", + "start": 443, + "end": 446, + "loc": { + "start": { + "line": 12, + "column": 7 + }, + "end": { + "line": 12, + "column": 10 + } + }, + "name": "fn1" + }, + "init": { + "type": "ArrowFunctionExpression", + "start": 449, + "end": 460, + "loc": { + "start": { + "line": 12, + "column": 13 + }, + "end": { + "line": 12, + "column": 24 + } + }, + "returnType": { + "type": "TSTypeAnnotation", + "start": 452, + "end": 455, + "loc": { + "start": { + "line": 12, + "column": 16 + }, + "end": { + "line": 12, + "column": 19 + } + }, + "typeAnnotation": { + "type": "TSTypeReference", + "start": 454, + "end": 455, + "loc": { + "start": { + "line": 12, + "column": 18 + }, + "end": { + "line": 12, + "column": 19 + } + }, + "typeName": { + "type": "Identifier", + "start": 454, + "end": 455, + "loc": { + "start": { + "line": 12, + "column": 18 + }, + "end": { + "line": 12, + "column": 19 + } + }, + "name": "T" + } + } + }, + "id": null, + "expression": true, + "generator": false, + "async": false, + "params": [ + { + "type": "Identifier", + "start": 450, + "end": 451, + "loc": { + "start": { + "line": 12, + "column": 14 + }, + "end": { + "line": 12, + "column": 15 + } + }, + "name": "p" + } + ], + "body": { + "type": "Literal", + "start": 459, + "end": 460, + "loc": { + "start": { + "line": 12, + "column": 23 + }, + "end": { + "line": 12, + "column": 24 + } + }, + "value": 1, + "raw": "1" + } + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " The `:` does annotate a signature here — plain return type", + "start": 374, + "end": 435 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 513, + "end": 544, + "loc": { + "start": { + "line": 15, + "column": 1 + }, + "end": { + "line": 15, + "column": 32 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 519, + "end": 543, + "loc": { + "start": { + "line": 15, + "column": 7 + }, + "end": { + "line": 15, + "column": 31 + } + }, + "id": { + "type": "Identifier", + "start": 519, + "end": 521, + "loc": { + "start": { + "line": 15, + "column": 7 + }, + "end": { + "line": 15, + "column": 9 + } + }, + "name": "s4" + }, + "init": { + "type": "ConditionalExpression", + "start": 524, + "end": 543, + "loc": { + "start": { + "line": 15, + "column": 12 + }, + "end": { + "line": 15, + "column": 31 + } + }, + "test": { + "type": "Identifier", + "start": 524, + "end": 525, + "loc": { + "start": { + "line": 15, + "column": 12 + }, + "end": { + "line": 15, + "column": 13 + } + }, + "name": "a" + }, + "consequent": { + "type": "ArrowFunctionExpression", + "start": 528, + "end": 539, + "loc": { + "start": { + "line": 15, + "column": 16 + }, + "end": { + "line": 15, + "column": 27 + } + }, + "returnType": { + "type": "TSTypeAnnotation", + "start": 531, + "end": 534, + "loc": { + "start": { + "line": 15, + "column": 19 + }, + "end": { + "line": 15, + "column": 22 + } + }, + "typeAnnotation": { + "type": "TSTypeReference", + "start": 533, + "end": 534, + "loc": { + "start": { + "line": 15, + "column": 21 + }, + "end": { + "line": 15, + "column": 22 + } + }, + "typeName": { + "type": "Identifier", + "start": 533, + "end": 534, + "loc": { + "start": { + "line": 15, + "column": 21 + }, + "end": { + "line": 15, + "column": 22 + } + }, + "name": "T" + } + } + }, + "id": null, + "expression": true, + "generator": false, + "async": false, + "params": [ + { + "type": "Identifier", + "start": 529, + "end": 530, + "loc": { + "start": { + "line": 15, + "column": 17 + }, + "end": { + "line": 15, + "column": 18 + } + }, + "name": "p" + } + ], + "body": { + "type": "Literal", + "start": 538, + "end": 539, + "loc": { + "start": { + "line": 15, + "column": 26 + }, + "end": { + "line": 15, + "column": 27 + } + }, + "value": 1, + "raw": "1" + } + }, + "alternate": { + "type": "Identifier", + "start": 542, + "end": 543, + "loc": { + "start": { + "line": 15, + "column": 30 + }, + "end": { + "line": 15, + "column": 31 + } + }, + "name": "d" + } + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " An annotated arrow as the ternary consequent", + "start": 464, + "end": 511 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 624, + "end": 666, + "loc": { + "start": { + "line": 18, + "column": 1 + }, + "end": { + "line": 18, + "column": 43 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 630, + "end": 665, + "loc": { + "start": { + "line": 18, + "column": 7 + }, + "end": { + "line": 18, + "column": 42 + } + }, + "id": { + "type": "Identifier", + "start": 630, + "end": 633, + "loc": { + "start": { + "line": 18, + "column": 7 + }, + "end": { + "line": 18, + "column": 10 + } + }, + "name": "fn2" + }, + "init": { + "type": "ArrowFunctionExpression", + "start": 636, + "end": 665, + "loc": { + "start": { + "line": 18, + "column": 13 + }, + "end": { + "line": 18, + "column": 42 + } + }, + "returnType": { + "type": "TSTypeAnnotation", + "start": 639, + "end": 660, + "loc": { + "start": { + "line": 18, + "column": 16 + }, + "end": { + "line": 18, + "column": 37 + } + }, + "typeAnnotation": { + "type": "TSConditionalType", + "start": 641, + "end": 660, + "loc": { + "start": { + "line": 18, + "column": 18 + }, + "end": { + "line": 18, + "column": 37 + } + }, + "checkType": { + "type": "TSTypeReference", + "start": 641, + "end": 642, + "loc": { + "start": { + "line": 18, + "column": 18 + }, + "end": { + "line": 18, + "column": 19 + } + }, + "typeName": { + "type": "Identifier", + "start": 641, + "end": 642, + "loc": { + "start": { + "line": 18, + "column": 18 + }, + "end": { + "line": 18, + "column": 19 + } + }, + "name": "A" + } + }, + "extendsType": { + "type": "TSTypeReference", + "start": 651, + "end": 652, + "loc": { + "start": { + "line": 18, + "column": 28 + }, + "end": { + "line": 18, + "column": 29 + } + }, + "typeName": { + "type": "Identifier", + "start": 651, + "end": 652, + "loc": { + "start": { + "line": 18, + "column": 28 + }, + "end": { + "line": 18, + "column": 29 + } + }, + "name": "B" + } + }, + "trueType": { + "type": "TSTypeReference", + "start": 655, + "end": 656, + "loc": { + "start": { + "line": 18, + "column": 32 + }, + "end": { + "line": 18, + "column": 33 + } + }, + "typeName": { + "type": "Identifier", + "start": 655, + "end": 656, + "loc": { + "start": { + "line": 18, + "column": 32 + }, + "end": { + "line": 18, + "column": 33 + } + }, + "name": "C" + } + }, + "falseType": { + "type": "TSTypeReference", + "start": 659, + "end": 660, + "loc": { + "start": { + "line": 18, + "column": 36 + }, + "end": { + "line": 18, + "column": 37 + } + }, + "typeName": { + "type": "Identifier", + "start": 659, + "end": 660, + "loc": { + "start": { + "line": 18, + "column": 36 + }, + "end": { + "line": 18, + "column": 37 + } + }, + "name": "D" + } + } + } + }, + "id": null, + "expression": true, + "generator": false, + "async": false, + "params": [ + { + "type": "Identifier", + "start": 637, + "end": 638, + "loc": { + "start": { + "line": 18, + "column": 14 + }, + "end": { + "line": 18, + "column": 15 + } + }, + "name": "p" + } + ], + "body": { + "type": "Identifier", + "start": 664, + "end": 665, + "loc": { + "start": { + "line": 18, + "column": 41 + }, + "end": { + "line": 18, + "column": 42 + } + }, + "name": "e" + } + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " Conditional type as the return type — its `?` and `:` belong to the type", + "start": 547, + "end": 622 + } + ] + } + ], + "sourceType": "module" + }, + "attributes": [ + { + "type": "Attribute", + "start": 8, + "end": 17, + "name": "lang", + "name_loc": { + "start": { + "line": 1, + "column": 8, + "character": 8 + }, + "end": { + "line": 1, + "column": 12, + "character": 12 + } + }, + "value": [ + { + "start": 14, + "end": 16, + "type": "Text", + "raw": "ts", + "data": "ts" + } + ] + } + ] + } +} diff --git a/tests/fixtures/typescript/expressions/arrow/return_type_ternary_colon/input.svelte b/tests/fixtures/typescript/expressions/arrow/return_type_ternary_colon/input.svelte new file mode 100644 index 000000000..90828a85f --- /dev/null +++ b/tests/fixtures/typescript/expressions/arrow/return_type_ternary_colon/input.svelte @@ -0,0 +1,19 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/chained/merged_head_callback_body_long/expected.json b/tests/fixtures/typescript/expressions/calls/chained/merged_head_callback_body_long/expected.json new file mode 100644 index 000000000..b3df55150 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/merged_head_callback_body_long/expected.json @@ -0,0 +1,783 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 499, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " 100 visual on the callback line - signature and body stay on one line", + "start": 38, + "end": 110, + "loc": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 74 + } + } + }, + { + "type": "Line", + "value": " 101 visual - the body breaks after `=>`, the signature stays intact", + "start": 262, + "end": 332, + "loc": { + "start": { + "line": 8, + "column": 2 + }, + "end": { + "line": 8, + "column": 72 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 498, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 489, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 14, + "column": 9 + } + }, + "body": [ + { + "type": "FunctionDeclaration", + "start": 20, + "end": 488, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 13, + "column": 2 + } + }, + "id": { + "type": "Identifier", + "start": 29, + "end": 31, + "loc": { + "start": { + "line": 2, + "column": 10 + }, + "end": { + "line": 2, + "column": 12 + } + }, + "name": "fn" + }, + "expression": false, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 34, + "end": 488, + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 13, + "column": 2 + } + }, + "body": [ + { + "type": "VariableDeclaration", + "start": 113, + "end": 258, + "loc": { + "start": { + "line": 4, + "column": 2 + }, + "end": { + "line": 6, + "column": 4 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 119, + "end": 257, + "loc": { + "start": { + "line": 4, + "column": 8 + }, + "end": { + "line": 6, + "column": 3 + } + }, + "id": { + "type": "Identifier", + "start": 119, + "end": 120, + "loc": { + "start": { + "line": 4, + "column": 8 + }, + "end": { + "line": 4, + "column": 9 + } + }, + "name": "a" + }, + "init": { + "type": "CallExpression", + "start": 123, + "end": 257, + "loc": { + "start": { + "line": 4, + "column": 12 + }, + "end": { + "line": 6, + "column": 3 + } + }, + "callee": { + "type": "MemberExpression", + "start": 123, + "end": 154, + "loc": { + "start": { + "line": 4, + "column": 12 + }, + "end": { + "line": 4, + "column": 43 + } + }, + "object": { + "type": "CallExpression", + "start": 123, + "end": 150, + "loc": { + "start": { + "line": 4, + "column": 12 + }, + "end": { + "line": 4, + "column": 39 + } + }, + "callee": { + "type": "MemberExpression", + "start": 123, + "end": 135, + "loc": { + "start": { + "line": 4, + "column": 12 + }, + "end": { + "line": 4, + "column": 24 + } + }, + "object": { + "type": "Identifier", + "start": 123, + "end": 128, + "loc": { + "start": { + "line": 4, + "column": 12 + }, + "end": { + "line": 4, + "column": 17 + } + }, + "name": "Items" + }, + "property": { + "type": "Identifier", + "start": 129, + "end": 135, + "loc": { + "start": { + "line": 4, + "column": 18 + }, + "end": { + "line": 4, + "column": 24 + } + }, + "name": "filter" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "ArrowFunctionExpression", + "start": 136, + "end": 149, + "loc": { + "start": { + "line": 4, + "column": 25 + }, + "end": { + "line": 4, + "column": 38 + } + }, + "id": null, + "expression": true, + "generator": false, + "async": false, + "params": [ + { + "type": "Identifier", + "start": 137, + "end": 138, + "loc": { + "start": { + "line": 4, + "column": 26 + }, + "end": { + "line": 4, + "column": 27 + } + }, + "name": "x" + } + ], + "body": { + "type": "CallExpression", + "start": 143, + "end": 149, + "loc": { + "start": { + "line": 4, + "column": 32 + }, + "end": { + "line": 4, + "column": 38 + } + }, + "callee": { + "type": "Identifier", + "start": 143, + "end": 146, + "loc": { + "start": { + "line": 4, + "column": 32 + }, + "end": { + "line": 4, + "column": 35 + } + }, + "name": "fn1" + }, + "arguments": [ + { + "type": "Identifier", + "start": 147, + "end": 148, + "loc": { + "start": { + "line": 4, + "column": 36 + }, + "end": { + "line": 4, + "column": 37 + } + }, + "name": "x" + } + ], + "optional": false + } + } + ], + "optional": false + }, + "property": { + "type": "Identifier", + "start": 151, + "end": 154, + "loc": { + "start": { + "line": 4, + "column": 40 + }, + "end": { + "line": 4, + "column": 43 + } + }, + "name": "map" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "ArrowFunctionExpression", + "start": 159, + "end": 253, + "loc": { + "start": { + "line": 5, + "column": 3 + }, + "end": { + "line": 5, + "column": 97 + } + }, + "id": null, + "expression": true, + "generator": false, + "async": false, + "params": [ + { + "type": "Identifier", + "start": 160, + "end": 185, + "loc": { + "start": { + "line": 5, + "column": 4 + }, + "end": { + "line": 5, + "column": 29 + } + }, + "name": "xxxxxxxxxxxxxxxxxxxxxxxxx" + } + ], + "body": { + "type": "Literal", + "start": 190, + "end": 253, + "loc": { + "start": { + "line": 5, + "column": 34 + }, + "end": { + "line": 5, + "column": 97 + } + }, + "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "raw": "'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'" + } + } + ], + "optional": false + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " 100 visual on the callback line - signature and body stay on one line", + "start": 38, + "end": 110 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 335, + "end": 485, + "loc": { + "start": { + "line": 9, + "column": 2 + }, + "end": { + "line": 12, + "column": 4 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 341, + "end": 484, + "loc": { + "start": { + "line": 9, + "column": 8 + }, + "end": { + "line": 12, + "column": 3 + } + }, + "id": { + "type": "Identifier", + "start": 341, + "end": 342, + "loc": { + "start": { + "line": 9, + "column": 8 + }, + "end": { + "line": 9, + "column": 9 + } + }, + "name": "b" + }, + "init": { + "type": "CallExpression", + "start": 345, + "end": 484, + "loc": { + "start": { + "line": 9, + "column": 12 + }, + "end": { + "line": 12, + "column": 3 + } + }, + "callee": { + "type": "MemberExpression", + "start": 345, + "end": 376, + "loc": { + "start": { + "line": 9, + "column": 12 + }, + "end": { + "line": 9, + "column": 43 + } + }, + "object": { + "type": "CallExpression", + "start": 345, + "end": 372, + "loc": { + "start": { + "line": 9, + "column": 12 + }, + "end": { + "line": 9, + "column": 39 + } + }, + "callee": { + "type": "MemberExpression", + "start": 345, + "end": 357, + "loc": { + "start": { + "line": 9, + "column": 12 + }, + "end": { + "line": 9, + "column": 24 + } + }, + "object": { + "type": "Identifier", + "start": 345, + "end": 350, + "loc": { + "start": { + "line": 9, + "column": 12 + }, + "end": { + "line": 9, + "column": 17 + } + }, + "name": "Items" + }, + "property": { + "type": "Identifier", + "start": 351, + "end": 357, + "loc": { + "start": { + "line": 9, + "column": 18 + }, + "end": { + "line": 9, + "column": 24 + } + }, + "name": "filter" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "ArrowFunctionExpression", + "start": 358, + "end": 371, + "loc": { + "start": { + "line": 9, + "column": 25 + }, + "end": { + "line": 9, + "column": 38 + } + }, + "id": null, + "expression": true, + "generator": false, + "async": false, + "params": [ + { + "type": "Identifier", + "start": 359, + "end": 360, + "loc": { + "start": { + "line": 9, + "column": 26 + }, + "end": { + "line": 9, + "column": 27 + } + }, + "name": "x" + } + ], + "body": { + "type": "CallExpression", + "start": 365, + "end": 371, + "loc": { + "start": { + "line": 9, + "column": 32 + }, + "end": { + "line": 9, + "column": 38 + } + }, + "callee": { + "type": "Identifier", + "start": 365, + "end": 368, + "loc": { + "start": { + "line": 9, + "column": 32 + }, + "end": { + "line": 9, + "column": 35 + } + }, + "name": "fn1" + }, + "arguments": [ + { + "type": "Identifier", + "start": 369, + "end": 370, + "loc": { + "start": { + "line": 9, + "column": 36 + }, + "end": { + "line": 9, + "column": 37 + } + }, + "name": "x" + } + ], + "optional": false + } + } + ], + "optional": false + }, + "property": { + "type": "Identifier", + "start": 373, + "end": 376, + "loc": { + "start": { + "line": 9, + "column": 40 + }, + "end": { + "line": 9, + "column": 43 + } + }, + "name": "map" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "ArrowFunctionExpression", + "start": 381, + "end": 480, + "loc": { + "start": { + "line": 10, + "column": 3 + }, + "end": { + "line": 11, + "column": 68 + } + }, + "id": null, + "expression": true, + "generator": false, + "async": false, + "params": [ + { + "type": "Identifier", + "start": 382, + "end": 407, + "loc": { + "start": { + "line": 10, + "column": 4 + }, + "end": { + "line": 10, + "column": 29 + } + }, + "name": "xxxxxxxxxxxxxxxxxxxxxxxxx" + } + ], + "body": { + "type": "Literal", + "start": 416, + "end": 480, + "loc": { + "start": { + "line": 11, + "column": 4 + }, + "end": { + "line": 11, + "column": 68 + } + }, + "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "raw": "'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'" + } + } + ], + "optional": false + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " 101 visual - the body breaks after `=>`, the signature stays intact", + "start": 262, + "end": 332 + } + ] + } + ] + } + } + ], + "sourceType": "module" + }, + "attributes": [ + { + "type": "Attribute", + "start": 8, + "end": 17, + "name": "lang", + "name_loc": { + "start": { + "line": 1, + "column": 8, + "character": 8 + }, + "end": { + "line": 1, + "column": 12, + "character": 12 + } + }, + "value": [ + { + "start": 14, + "end": 16, + "type": "Text", + "raw": "ts", + "data": "ts" + } + ] + } + ] + } +} diff --git a/tests/fixtures/typescript/expressions/calls/chained/merged_head_callback_body_long/input.svelte b/tests/fixtures/typescript/expressions/calls/chained/merged_head_callback_body_long/input.svelte new file mode 100644 index 000000000..859a09885 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/merged_head_callback_body_long/input.svelte @@ -0,0 +1,14 @@ + diff --git a/tests/fixtures/typescript/expressions/calls/chained/merged_head_callback_body_long/unformatted_compact.svelte b/tests/fixtures/typescript/expressions/calls/chained/merged_head_callback_body_long/unformatted_compact.svelte new file mode 100644 index 000000000..a7d3e7863 --- /dev/null +++ b/tests/fixtures/typescript/expressions/calls/chained/merged_head_callback_body_long/unformatted_compact.svelte @@ -0,0 +1,9 @@ + diff --git a/tests/fixtures/typescript/expressions/literals/regex/comment_delimiter_body/expected.json b/tests/fixtures/typescript/expressions/literals/regex/comment_delimiter_body/expected.json new file mode 100644 index 000000000..c344b1737 --- /dev/null +++ b/tests/fixtures/typescript/expressions/literals/regex/comment_delimiter_body/expected.json @@ -0,0 +1,1275 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 588, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " Regex body ending in an escaped slash — the `//` is regex, not a line comment", + "start": 33, + "end": 113, + "loc": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 82 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 149, + "end": 157, + "loc": { + "start": { + "line": 6, + "column": 2 + }, + "end": { + "line": 6, + "column": 10 + } + } + }, + { + "type": "Line", + "value": " `//` inside a character class", + "start": 193, + "end": 225, + "loc": { + "start": { + "line": 11, + "column": 2 + }, + "end": { + "line": 11, + "column": 34 + } + } + }, + { + "type": "Block", + "value": " c2 ", + "start": 263, + "end": 271, + "loc": { + "start": { + "line": 14, + "column": 2 + }, + "end": { + "line": 14, + "column": 10 + } + } + }, + { + "type": "Line", + "value": " Regex body containing `/*` — not a block comment", + "start": 307, + "end": 358, + "loc": { + "start": { + "line": 19, + "column": 2 + }, + "end": { + "line": 19, + "column": 53 + } + } + }, + { + "type": "Block", + "value": " c3 ", + "start": 395, + "end": 403, + "loc": { + "start": { + "line": 22, + "column": 2 + }, + "end": { + "line": 22, + "column": 10 + } + } + }, + { + "type": "Line", + "value": " Escaped slash away from the delimiters — no comment bytes in the body", + "start": 439, + "end": 511, + "loc": { + "start": { + "line": 27, + "column": 2 + }, + "end": { + "line": 27, + "column": 74 + } + } + }, + { + "type": "Block", + "value": " c4 ", + "start": 549, + "end": 557, + "loc": { + "start": { + "line": 30, + "column": 2 + }, + "end": { + "line": 30, + "column": 10 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 587, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 578, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 33, + "column": 9 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 20, + "end": 177, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 8, + "column": 4 + } + }, + "expression": { + "type": "CallExpression", + "start": 20, + "end": 176, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 8, + "column": 3 + } + }, + "callee": { + "type": "Identifier", + "start": 20, + "end": 22, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "ArrowFunctionExpression", + "start": 23, + "end": 175, + "loc": { + "start": { + "line": 2, + "column": 4 + }, + "end": { + "line": 8, + "column": 2 + } + }, + "id": null, + "expression": false, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 29, + "end": 175, + "loc": { + "start": { + "line": 2, + "column": 10 + }, + "end": { + "line": 8, + "column": 2 + } + }, + "body": [ + { + "type": "IfStatement", + "start": 116, + "end": 145, + "loc": { + "start": { + "line": 4, + "column": 2 + }, + "end": { + "line": 4, + "column": 31 + } + }, + "test": { + "type": "CallExpression", + "start": 120, + "end": 132, + "loc": { + "start": { + "line": 4, + "column": 6 + }, + "end": { + "line": 4, + "column": 18 + } + }, + "callee": { + "type": "MemberExpression", + "start": 120, + "end": 129, + "loc": { + "start": { + "line": 4, + "column": 6 + }, + "end": { + "line": 4, + "column": 15 + } + }, + "object": { + "type": "Literal", + "start": 120, + "end": 124, + "loc": { + "start": { + "line": 4, + "column": 6 + }, + "end": { + "line": 4, + "column": 10 + } + }, + "value": {}, + "raw": "/\\//", + "regex": { + "pattern": "\\/", + "flags": "" + } + }, + "property": { + "type": "Identifier", + "start": 125, + "end": 129, + "loc": { + "start": { + "line": 4, + "column": 11 + }, + "end": { + "line": 4, + "column": 15 + } + }, + "name": "test" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "Identifier", + "start": 130, + "end": 131, + "loc": { + "start": { + "line": 4, + "column": 16 + }, + "end": { + "line": 4, + "column": 17 + } + }, + "name": "a" + } + ], + "optional": false + }, + "consequent": { + "type": "ReturnStatement", + "start": 134, + "end": 145, + "loc": { + "start": { + "line": 4, + "column": 20 + }, + "end": { + "line": 4, + "column": 31 + } + }, + "argument": { + "type": "Literal", + "start": 141, + "end": 144, + "loc": { + "start": { + "line": 4, + "column": 27 + }, + "end": { + "line": 4, + "column": 30 + } + }, + "value": "x", + "raw": "'x'" + } + }, + "alternate": null, + "leadingComments": [ + { + "type": "Line", + "value": " Regex body ending in an escaped slash — the `//` is regex, not a line comment", + "start": 33, + "end": 113 + } + ] + }, + { + "type": "ReturnStatement", + "start": 160, + "end": 172, + "loc": { + "start": { + "line": 7, + "column": 2 + }, + "end": { + "line": 7, + "column": 14 + } + }, + "argument": { + "type": "Literal", + "start": 167, + "end": 171, + "loc": { + "start": { + "line": 7, + "column": 9 + }, + "end": { + "line": 7, + "column": 13 + } + }, + "value": null, + "raw": "null" + }, + "leadingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 149, + "end": 157 + } + ] + } + ] + } + } + ], + "optional": false + } + }, + { + "type": "ExpressionStatement", + "start": 180, + "end": 291, + "loc": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 16, + "column": 4 + } + }, + "expression": { + "type": "CallExpression", + "start": 180, + "end": 290, + "loc": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 16, + "column": 3 + } + }, + "callee": { + "type": "Identifier", + "start": 180, + "end": 182, + "loc": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 10, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "ArrowFunctionExpression", + "start": 183, + "end": 289, + "loc": { + "start": { + "line": 10, + "column": 4 + }, + "end": { + "line": 16, + "column": 2 + } + }, + "id": null, + "expression": false, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 189, + "end": 289, + "loc": { + "start": { + "line": 10, + "column": 10 + }, + "end": { + "line": 16, + "column": 2 + } + }, + "body": [ + { + "type": "IfStatement", + "start": 228, + "end": 259, + "loc": { + "start": { + "line": 12, + "column": 2 + }, + "end": { + "line": 12, + "column": 33 + } + }, + "test": { + "type": "CallExpression", + "start": 232, + "end": 246, + "loc": { + "start": { + "line": 12, + "column": 6 + }, + "end": { + "line": 12, + "column": 20 + } + }, + "callee": { + "type": "MemberExpression", + "start": 232, + "end": 243, + "loc": { + "start": { + "line": 12, + "column": 6 + }, + "end": { + "line": 12, + "column": 17 + } + }, + "object": { + "type": "Literal", + "start": 232, + "end": 238, + "loc": { + "start": { + "line": 12, + "column": 6 + }, + "end": { + "line": 12, + "column": 12 + } + }, + "value": {}, + "raw": "/[//]/", + "regex": { + "pattern": "[//]", + "flags": "" + } + }, + "property": { + "type": "Identifier", + "start": 239, + "end": 243, + "loc": { + "start": { + "line": 12, + "column": 13 + }, + "end": { + "line": 12, + "column": 17 + } + }, + "name": "test" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "Identifier", + "start": 244, + "end": 245, + "loc": { + "start": { + "line": 12, + "column": 18 + }, + "end": { + "line": 12, + "column": 19 + } + }, + "name": "a" + } + ], + "optional": false + }, + "consequent": { + "type": "ReturnStatement", + "start": 248, + "end": 259, + "loc": { + "start": { + "line": 12, + "column": 22 + }, + "end": { + "line": 12, + "column": 33 + } + }, + "argument": { + "type": "Literal", + "start": 255, + "end": 258, + "loc": { + "start": { + "line": 12, + "column": 29 + }, + "end": { + "line": 12, + "column": 32 + } + }, + "value": "x", + "raw": "'x'" + } + }, + "alternate": null, + "leadingComments": [ + { + "type": "Line", + "value": " `//` inside a character class", + "start": 193, + "end": 225 + } + ] + }, + { + "type": "ReturnStatement", + "start": 274, + "end": 286, + "loc": { + "start": { + "line": 15, + "column": 2 + }, + "end": { + "line": 15, + "column": 14 + } + }, + "argument": { + "type": "Literal", + "start": 281, + "end": 285, + "loc": { + "start": { + "line": 15, + "column": 9 + }, + "end": { + "line": 15, + "column": 13 + } + }, + "value": null, + "raw": "null" + }, + "leadingComments": [ + { + "type": "Block", + "value": " c2 ", + "start": 263, + "end": 271 + } + ] + } + ] + } + } + ], + "optional": false + } + }, + { + "type": "ExpressionStatement", + "start": 294, + "end": 423, + "loc": { + "start": { + "line": 18, + "column": 1 + }, + "end": { + "line": 24, + "column": 4 + } + }, + "expression": { + "type": "CallExpression", + "start": 294, + "end": 422, + "loc": { + "start": { + "line": 18, + "column": 1 + }, + "end": { + "line": 24, + "column": 3 + } + }, + "callee": { + "type": "Identifier", + "start": 294, + "end": 296, + "loc": { + "start": { + "line": 18, + "column": 1 + }, + "end": { + "line": 18, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "ArrowFunctionExpression", + "start": 297, + "end": 421, + "loc": { + "start": { + "line": 18, + "column": 4 + }, + "end": { + "line": 24, + "column": 2 + } + }, + "id": null, + "expression": false, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 303, + "end": 421, + "loc": { + "start": { + "line": 18, + "column": 10 + }, + "end": { + "line": 24, + "column": 2 + } + }, + "body": [ + { + "type": "IfStatement", + "start": 361, + "end": 391, + "loc": { + "start": { + "line": 20, + "column": 2 + }, + "end": { + "line": 20, + "column": 32 + } + }, + "test": { + "type": "CallExpression", + "start": 365, + "end": 378, + "loc": { + "start": { + "line": 20, + "column": 6 + }, + "end": { + "line": 20, + "column": 19 + } + }, + "callee": { + "type": "MemberExpression", + "start": 365, + "end": 375, + "loc": { + "start": { + "line": 20, + "column": 6 + }, + "end": { + "line": 20, + "column": 16 + } + }, + "object": { + "type": "Literal", + "start": 365, + "end": 370, + "loc": { + "start": { + "line": 20, + "column": 6 + }, + "end": { + "line": 20, + "column": 11 + } + }, + "value": {}, + "raw": "/\\/*/", + "regex": { + "pattern": "\\/*", + "flags": "" + } + }, + "property": { + "type": "Identifier", + "start": 371, + "end": 375, + "loc": { + "start": { + "line": 20, + "column": 12 + }, + "end": { + "line": 20, + "column": 16 + } + }, + "name": "test" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "Identifier", + "start": 376, + "end": 377, + "loc": { + "start": { + "line": 20, + "column": 17 + }, + "end": { + "line": 20, + "column": 18 + } + }, + "name": "a" + } + ], + "optional": false + }, + "consequent": { + "type": "ReturnStatement", + "start": 380, + "end": 391, + "loc": { + "start": { + "line": 20, + "column": 21 + }, + "end": { + "line": 20, + "column": 32 + } + }, + "argument": { + "type": "Literal", + "start": 387, + "end": 390, + "loc": { + "start": { + "line": 20, + "column": 28 + }, + "end": { + "line": 20, + "column": 31 + } + }, + "value": "x", + "raw": "'x'" + } + }, + "alternate": null, + "leadingComments": [ + { + "type": "Line", + "value": " Regex body containing `/*` — not a block comment", + "start": 307, + "end": 358 + } + ] + }, + { + "type": "ReturnStatement", + "start": 406, + "end": 418, + "loc": { + "start": { + "line": 23, + "column": 2 + }, + "end": { + "line": 23, + "column": 14 + } + }, + "argument": { + "type": "Literal", + "start": 413, + "end": 417, + "loc": { + "start": { + "line": 23, + "column": 9 + }, + "end": { + "line": 23, + "column": 13 + } + }, + "value": null, + "raw": "null" + }, + "leadingComments": [ + { + "type": "Block", + "value": " c3 ", + "start": 395, + "end": 403 + } + ] + } + ] + } + } + ], + "optional": false + } + }, + { + "type": "ExpressionStatement", + "start": 426, + "end": 577, + "loc": { + "start": { + "line": 26, + "column": 1 + }, + "end": { + "line": 32, + "column": 4 + } + }, + "expression": { + "type": "CallExpression", + "start": 426, + "end": 576, + "loc": { + "start": { + "line": 26, + "column": 1 + }, + "end": { + "line": 32, + "column": 3 + } + }, + "callee": { + "type": "Identifier", + "start": 426, + "end": 428, + "loc": { + "start": { + "line": 26, + "column": 1 + }, + "end": { + "line": 26, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "ArrowFunctionExpression", + "start": 429, + "end": 575, + "loc": { + "start": { + "line": 26, + "column": 4 + }, + "end": { + "line": 32, + "column": 2 + } + }, + "id": null, + "expression": false, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 435, + "end": 575, + "loc": { + "start": { + "line": 26, + "column": 10 + }, + "end": { + "line": 32, + "column": 2 + } + }, + "body": [ + { + "type": "IfStatement", + "start": 514, + "end": 545, + "loc": { + "start": { + "line": 28, + "column": 2 + }, + "end": { + "line": 28, + "column": 33 + } + }, + "test": { + "type": "CallExpression", + "start": 518, + "end": 532, + "loc": { + "start": { + "line": 28, + "column": 6 + }, + "end": { + "line": 28, + "column": 20 + } + }, + "callee": { + "type": "MemberExpression", + "start": 518, + "end": 529, + "loc": { + "start": { + "line": 28, + "column": 6 + }, + "end": { + "line": 28, + "column": 17 + } + }, + "object": { + "type": "Literal", + "start": 518, + "end": 524, + "loc": { + "start": { + "line": 28, + "column": 6 + }, + "end": { + "line": 28, + "column": 12 + } + }, + "value": {}, + "raw": "/a\\/b/", + "regex": { + "pattern": "a\\/b", + "flags": "" + } + }, + "property": { + "type": "Identifier", + "start": 525, + "end": 529, + "loc": { + "start": { + "line": 28, + "column": 13 + }, + "end": { + "line": 28, + "column": 17 + } + }, + "name": "test" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "Identifier", + "start": 530, + "end": 531, + "loc": { + "start": { + "line": 28, + "column": 18 + }, + "end": { + "line": 28, + "column": 19 + } + }, + "name": "a" + } + ], + "optional": false + }, + "consequent": { + "type": "ReturnStatement", + "start": 534, + "end": 545, + "loc": { + "start": { + "line": 28, + "column": 22 + }, + "end": { + "line": 28, + "column": 33 + } + }, + "argument": { + "type": "Literal", + "start": 541, + "end": 544, + "loc": { + "start": { + "line": 28, + "column": 29 + }, + "end": { + "line": 28, + "column": 32 + } + }, + "value": "x", + "raw": "'x'" + } + }, + "alternate": null, + "leadingComments": [ + { + "type": "Line", + "value": " Escaped slash away from the delimiters — no comment bytes in the body", + "start": 439, + "end": 511 + } + ] + }, + { + "type": "ReturnStatement", + "start": 560, + "end": 572, + "loc": { + "start": { + "line": 31, + "column": 2 + }, + "end": { + "line": 31, + "column": 14 + } + }, + "argument": { + "type": "Literal", + "start": 567, + "end": 571, + "loc": { + "start": { + "line": 31, + "column": 9 + }, + "end": { + "line": 31, + "column": 13 + } + }, + "value": null, + "raw": "null" + }, + "leadingComments": [ + { + "type": "Block", + "value": " c4 ", + "start": 549, + "end": 557 + } + ] + } + ] + } + } + ], + "optional": false + } + } + ], + "sourceType": "module" + }, + "attributes": [ + { + "type": "Attribute", + "start": 8, + "end": 17, + "name": "lang", + "name_loc": { + "start": { + "line": 1, + "column": 8, + "character": 8 + }, + "end": { + "line": 1, + "column": 12, + "character": 12 + } + }, + "value": [ + { + "start": 14, + "end": 16, + "type": "Text", + "raw": "ts", + "data": "ts" + } + ] + } + ] + } +} diff --git a/tests/fixtures/typescript/expressions/literals/regex/comment_delimiter_body/input.svelte b/tests/fixtures/typescript/expressions/literals/regex/comment_delimiter_body/input.svelte new file mode 100644 index 000000000..4fc493370 --- /dev/null +++ b/tests/fixtures/typescript/expressions/literals/regex/comment_delimiter_body/input.svelte @@ -0,0 +1,33 @@ + diff --git a/tests/fixtures/typescript/expressions/literals/regex/keyword_operand_slash/expected.json b/tests/fixtures/typescript/expressions/literals/regex/keyword_operand_slash/expected.json new file mode 100644 index 000000000..80ed5345d --- /dev/null +++ b/tests/fixtures/typescript/expressions/literals/regex/keyword_operand_slash/expected.json @@ -0,0 +1,1495 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 706, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " After `typeof` the `/` opens a regex — the keyword is an operator, not an operand", + "start": 33, + "end": 117, + "loc": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 86 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 165, + "end": 173, + "loc": { + "start": { + "line": 6, + "column": 2 + }, + "end": { + "line": 6, + "column": 10 + } + } + }, + { + "type": "Line", + "value": " Same after `void`", + "start": 209, + "end": 229, + "loc": { + "start": { + "line": 11, + "column": 2 + }, + "end": { + "line": 11, + "column": 22 + } + } + }, + { + "type": "Block", + "value": " c2 ", + "start": 262, + "end": 270, + "loc": { + "start": { + "line": 14, + "column": 2 + }, + "end": { + "line": 14, + "column": 10 + } + } + }, + { + "type": "Line", + "value": " Same after the `in` operator", + "start": 306, + "end": 337, + "loc": { + "start": { + "line": 19, + "column": 2 + }, + "end": { + "line": 19, + "column": 33 + } + } + }, + { + "type": "Block", + "value": " c3 ", + "start": 372, + "end": 380, + "loc": { + "start": { + "line": 22, + "column": 2 + }, + "end": { + "line": 22, + "column": 10 + } + } + }, + { + "type": "Line", + "value": " An identifier before `/` is an operand, so this is division, not a regex", + "start": 416, + "end": 491, + "loc": { + "start": { + "line": 27, + "column": 2 + }, + "end": { + "line": 27, + "column": 77 + } + } + }, + { + "type": "Block", + "value": " c4 ", + "start": 526, + "end": 534, + "loc": { + "start": { + "line": 30, + "column": 2 + }, + "end": { + "line": 30, + "column": 10 + } + } + }, + { + "type": "Line", + "value": " A reserved word used as a property name is an operand too", + "start": 570, + "end": 630, + "loc": { + "start": { + "line": 35, + "column": 2 + }, + "end": { + "line": 35, + "column": 62 + } + } + }, + { + "type": "Block", + "value": " c5 ", + "start": 667, + "end": 675, + "loc": { + "start": { + "line": 38, + "column": 2 + }, + "end": { + "line": 38, + "column": 10 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 705, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 696, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 41, + "column": 9 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 20, + "end": 193, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 8, + "column": 4 + } + }, + "expression": { + "type": "CallExpression", + "start": 20, + "end": 192, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 8, + "column": 3 + } + }, + "callee": { + "type": "Identifier", + "start": 20, + "end": 22, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "ArrowFunctionExpression", + "start": 23, + "end": 191, + "loc": { + "start": { + "line": 2, + "column": 4 + }, + "end": { + "line": 8, + "column": 2 + } + }, + "id": null, + "expression": false, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 29, + "end": 191, + "loc": { + "start": { + "line": 2, + "column": 10 + }, + "end": { + "line": 8, + "column": 2 + } + }, + "body": [ + { + "type": "IfStatement", + "start": 120, + "end": 161, + "loc": { + "start": { + "line": 4, + "column": 2 + }, + "end": { + "line": 4, + "column": 43 + } + }, + "test": { + "type": "BinaryExpression", + "start": 124, + "end": 148, + "loc": { + "start": { + "line": 4, + "column": 6 + }, + "end": { + "line": 4, + "column": 30 + } + }, + "left": { + "type": "UnaryExpression", + "start": 124, + "end": 135, + "loc": { + "start": { + "line": 4, + "column": 6 + }, + "end": { + "line": 4, + "column": 17 + } + }, + "operator": "typeof", + "prefix": true, + "argument": { + "type": "Literal", + "start": 131, + "end": 135, + "loc": { + "start": { + "line": 4, + "column": 13 + }, + "end": { + "line": 4, + "column": 17 + } + }, + "value": {}, + "raw": "/\\//", + "regex": { + "pattern": "\\/", + "flags": "" + } + } + }, + "operator": "===", + "right": { + "type": "Literal", + "start": 140, + "end": 148, + "loc": { + "start": { + "line": 4, + "column": 22 + }, + "end": { + "line": 4, + "column": 30 + } + }, + "value": "object", + "raw": "'object'" + } + }, + "consequent": { + "type": "ReturnStatement", + "start": 150, + "end": 161, + "loc": { + "start": { + "line": 4, + "column": 32 + }, + "end": { + "line": 4, + "column": 43 + } + }, + "argument": { + "type": "Literal", + "start": 157, + "end": 160, + "loc": { + "start": { + "line": 4, + "column": 39 + }, + "end": { + "line": 4, + "column": 42 + } + }, + "value": "x", + "raw": "'x'" + } + }, + "alternate": null, + "leadingComments": [ + { + "type": "Line", + "value": " After `typeof` the `/` opens a regex — the keyword is an operator, not an operand", + "start": 33, + "end": 117 + } + ] + }, + { + "type": "ReturnStatement", + "start": 176, + "end": 188, + "loc": { + "start": { + "line": 7, + "column": 2 + }, + "end": { + "line": 7, + "column": 14 + } + }, + "argument": { + "type": "Literal", + "start": 183, + "end": 187, + "loc": { + "start": { + "line": 7, + "column": 9 + }, + "end": { + "line": 7, + "column": 13 + } + }, + "value": null, + "raw": "null" + }, + "leadingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 165, + "end": 173 + } + ] + } + ] + } + } + ], + "optional": false + } + }, + { + "type": "ExpressionStatement", + "start": 196, + "end": 290, + "loc": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 16, + "column": 4 + } + }, + "expression": { + "type": "CallExpression", + "start": 196, + "end": 289, + "loc": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 16, + "column": 3 + } + }, + "callee": { + "type": "Identifier", + "start": 196, + "end": 198, + "loc": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 10, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "ArrowFunctionExpression", + "start": 199, + "end": 288, + "loc": { + "start": { + "line": 10, + "column": 4 + }, + "end": { + "line": 16, + "column": 2 + } + }, + "id": null, + "expression": false, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 205, + "end": 288, + "loc": { + "start": { + "line": 10, + "column": 10 + }, + "end": { + "line": 16, + "column": 2 + } + }, + "body": [ + { + "type": "IfStatement", + "start": 232, + "end": 258, + "loc": { + "start": { + "line": 12, + "column": 2 + }, + "end": { + "line": 12, + "column": 28 + } + }, + "test": { + "type": "UnaryExpression", + "start": 236, + "end": 245, + "loc": { + "start": { + "line": 12, + "column": 6 + }, + "end": { + "line": 12, + "column": 15 + } + }, + "operator": "void", + "prefix": true, + "argument": { + "type": "Literal", + "start": 241, + "end": 245, + "loc": { + "start": { + "line": 12, + "column": 11 + }, + "end": { + "line": 12, + "column": 15 + } + }, + "value": {}, + "raw": "/\\//", + "regex": { + "pattern": "\\/", + "flags": "" + } + } + }, + "consequent": { + "type": "ReturnStatement", + "start": 247, + "end": 258, + "loc": { + "start": { + "line": 12, + "column": 17 + }, + "end": { + "line": 12, + "column": 28 + } + }, + "argument": { + "type": "Literal", + "start": 254, + "end": 257, + "loc": { + "start": { + "line": 12, + "column": 24 + }, + "end": { + "line": 12, + "column": 27 + } + }, + "value": "x", + "raw": "'x'" + } + }, + "alternate": null, + "leadingComments": [ + { + "type": "Line", + "value": " Same after `void`", + "start": 209, + "end": 229 + } + ] + }, + { + "type": "ReturnStatement", + "start": 273, + "end": 285, + "loc": { + "start": { + "line": 15, + "column": 2 + }, + "end": { + "line": 15, + "column": 14 + } + }, + "argument": { + "type": "Literal", + "start": 280, + "end": 284, + "loc": { + "start": { + "line": 15, + "column": 9 + }, + "end": { + "line": 15, + "column": 13 + } + }, + "value": null, + "raw": "null" + }, + "leadingComments": [ + { + "type": "Block", + "value": " c2 ", + "start": 262, + "end": 270 + } + ] + } + ] + } + } + ], + "optional": false + } + }, + { + "type": "ExpressionStatement", + "start": 293, + "end": 400, + "loc": { + "start": { + "line": 18, + "column": 1 + }, + "end": { + "line": 24, + "column": 4 + } + }, + "expression": { + "type": "CallExpression", + "start": 293, + "end": 399, + "loc": { + "start": { + "line": 18, + "column": 1 + }, + "end": { + "line": 24, + "column": 3 + } + }, + "callee": { + "type": "Identifier", + "start": 293, + "end": 295, + "loc": { + "start": { + "line": 18, + "column": 1 + }, + "end": { + "line": 18, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "ArrowFunctionExpression", + "start": 296, + "end": 398, + "loc": { + "start": { + "line": 18, + "column": 4 + }, + "end": { + "line": 24, + "column": 2 + } + }, + "id": null, + "expression": false, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 302, + "end": 398, + "loc": { + "start": { + "line": 18, + "column": 10 + }, + "end": { + "line": 24, + "column": 2 + } + }, + "body": [ + { + "type": "IfStatement", + "start": 340, + "end": 368, + "loc": { + "start": { + "line": 20, + "column": 2 + }, + "end": { + "line": 20, + "column": 30 + } + }, + "test": { + "type": "BinaryExpression", + "start": 344, + "end": 355, + "loc": { + "start": { + "line": 20, + "column": 6 + }, + "end": { + "line": 20, + "column": 17 + } + }, + "left": { + "type": "Literal", + "start": 344, + "end": 347, + "loc": { + "start": { + "line": 20, + "column": 6 + }, + "end": { + "line": 20, + "column": 9 + } + }, + "value": "a", + "raw": "'a'" + }, + "operator": "in", + "right": { + "type": "Literal", + "start": 351, + "end": 355, + "loc": { + "start": { + "line": 20, + "column": 13 + }, + "end": { + "line": 20, + "column": 17 + } + }, + "value": {}, + "raw": "/\\//", + "regex": { + "pattern": "\\/", + "flags": "" + } + } + }, + "consequent": { + "type": "ReturnStatement", + "start": 357, + "end": 368, + "loc": { + "start": { + "line": 20, + "column": 19 + }, + "end": { + "line": 20, + "column": 30 + } + }, + "argument": { + "type": "Literal", + "start": 364, + "end": 367, + "loc": { + "start": { + "line": 20, + "column": 26 + }, + "end": { + "line": 20, + "column": 29 + } + }, + "value": "x", + "raw": "'x'" + } + }, + "alternate": null, + "leadingComments": [ + { + "type": "Line", + "value": " Same after the `in` operator", + "start": 306, + "end": 337 + } + ] + }, + { + "type": "ReturnStatement", + "start": 383, + "end": 395, + "loc": { + "start": { + "line": 23, + "column": 2 + }, + "end": { + "line": 23, + "column": 14 + } + }, + "argument": { + "type": "Literal", + "start": 390, + "end": 394, + "loc": { + "start": { + "line": 23, + "column": 9 + }, + "end": { + "line": 23, + "column": 13 + } + }, + "value": null, + "raw": "null" + }, + "leadingComments": [ + { + "type": "Block", + "value": " c3 ", + "start": 372, + "end": 380 + } + ] + } + ] + } + } + ], + "optional": false + } + }, + { + "type": "ExpressionStatement", + "start": 403, + "end": 554, + "loc": { + "start": { + "line": 26, + "column": 1 + }, + "end": { + "line": 32, + "column": 4 + } + }, + "expression": { + "type": "CallExpression", + "start": 403, + "end": 553, + "loc": { + "start": { + "line": 26, + "column": 1 + }, + "end": { + "line": 32, + "column": 3 + } + }, + "callee": { + "type": "Identifier", + "start": 403, + "end": 405, + "loc": { + "start": { + "line": 26, + "column": 1 + }, + "end": { + "line": 26, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "ArrowFunctionExpression", + "start": 406, + "end": 552, + "loc": { + "start": { + "line": 26, + "column": 4 + }, + "end": { + "line": 32, + "column": 2 + } + }, + "id": null, + "expression": false, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 412, + "end": 552, + "loc": { + "start": { + "line": 26, + "column": 10 + }, + "end": { + "line": 32, + "column": 2 + } + }, + "body": [ + { + "type": "IfStatement", + "start": 494, + "end": 522, + "loc": { + "start": { + "line": 28, + "column": 2 + }, + "end": { + "line": 28, + "column": 30 + } + }, + "test": { + "type": "BinaryExpression", + "start": 498, + "end": 509, + "loc": { + "start": { + "line": 28, + "column": 6 + }, + "end": { + "line": 28, + "column": 17 + } + }, + "left": { + "type": "BinaryExpression", + "start": 498, + "end": 505, + "loc": { + "start": { + "line": 28, + "column": 6 + }, + "end": { + "line": 28, + "column": 13 + } + }, + "left": { + "type": "Identifier", + "start": 498, + "end": 500, + "loc": { + "start": { + "line": 28, + "column": 6 + }, + "end": { + "line": 28, + "column": 8 + } + }, + "name": "aa" + }, + "operator": "/", + "right": { + "type": "Identifier", + "start": 503, + "end": 505, + "loc": { + "start": { + "line": 28, + "column": 11 + }, + "end": { + "line": 28, + "column": 13 + } + }, + "name": "bb" + } + }, + "operator": ">", + "right": { + "type": "Literal", + "start": 508, + "end": 509, + "loc": { + "start": { + "line": 28, + "column": 16 + }, + "end": { + "line": 28, + "column": 17 + } + }, + "value": 1, + "raw": "1" + } + }, + "consequent": { + "type": "ReturnStatement", + "start": 511, + "end": 522, + "loc": { + "start": { + "line": 28, + "column": 19 + }, + "end": { + "line": 28, + "column": 30 + } + }, + "argument": { + "type": "Literal", + "start": 518, + "end": 521, + "loc": { + "start": { + "line": 28, + "column": 26 + }, + "end": { + "line": 28, + "column": 29 + } + }, + "value": "x", + "raw": "'x'" + } + }, + "alternate": null, + "leadingComments": [ + { + "type": "Line", + "value": " An identifier before `/` is an operand, so this is division, not a regex", + "start": 416, + "end": 491 + } + ] + }, + { + "type": "ReturnStatement", + "start": 537, + "end": 549, + "loc": { + "start": { + "line": 31, + "column": 2 + }, + "end": { + "line": 31, + "column": 14 + } + }, + "argument": { + "type": "Literal", + "start": 544, + "end": 548, + "loc": { + "start": { + "line": 31, + "column": 9 + }, + "end": { + "line": 31, + "column": 13 + } + }, + "value": null, + "raw": "null" + }, + "leadingComments": [ + { + "type": "Block", + "value": " c4 ", + "start": 526, + "end": 534 + } + ] + } + ] + } + } + ], + "optional": false + } + }, + { + "type": "ExpressionStatement", + "start": 557, + "end": 695, + "loc": { + "start": { + "line": 34, + "column": 1 + }, + "end": { + "line": 40, + "column": 4 + } + }, + "expression": { + "type": "CallExpression", + "start": 557, + "end": 694, + "loc": { + "start": { + "line": 34, + "column": 1 + }, + "end": { + "line": 40, + "column": 3 + } + }, + "callee": { + "type": "Identifier", + "start": 557, + "end": 559, + "loc": { + "start": { + "line": 34, + "column": 1 + }, + "end": { + "line": 34, + "column": 3 + } + }, + "name": "fn" + }, + "arguments": [ + { + "type": "ArrowFunctionExpression", + "start": 560, + "end": 693, + "loc": { + "start": { + "line": 34, + "column": 4 + }, + "end": { + "line": 40, + "column": 2 + } + }, + "id": null, + "expression": false, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 566, + "end": 693, + "loc": { + "start": { + "line": 34, + "column": 10 + }, + "end": { + "line": 40, + "column": 2 + } + }, + "body": [ + { + "type": "IfStatement", + "start": 633, + "end": 663, + "loc": { + "start": { + "line": 36, + "column": 2 + }, + "end": { + "line": 36, + "column": 32 + } + }, + "test": { + "type": "BinaryExpression", + "start": 637, + "end": 650, + "loc": { + "start": { + "line": 36, + "column": 6 + }, + "end": { + "line": 36, + "column": 19 + } + }, + "left": { + "type": "BinaryExpression", + "start": 637, + "end": 646, + "loc": { + "start": { + "line": 36, + "column": 6 + }, + "end": { + "line": 36, + "column": 15 + } + }, + "left": { + "type": "MemberExpression", + "start": 637, + "end": 641, + "loc": { + "start": { + "line": 36, + "column": 6 + }, + "end": { + "line": 36, + "column": 10 + } + }, + "object": { + "type": "Identifier", + "start": 637, + "end": 638, + "loc": { + "start": { + "line": 36, + "column": 6 + }, + "end": { + "line": 36, + "column": 7 + } + }, + "name": "a" + }, + "property": { + "type": "Identifier", + "start": 639, + "end": 641, + "loc": { + "start": { + "line": 36, + "column": 8 + }, + "end": { + "line": 36, + "column": 10 + } + }, + "name": "in" + }, + "computed": false, + "optional": false + }, + "operator": "/", + "right": { + "type": "Identifier", + "start": 644, + "end": 646, + "loc": { + "start": { + "line": 36, + "column": 13 + }, + "end": { + "line": 36, + "column": 15 + } + }, + "name": "bb" + } + }, + "operator": ">", + "right": { + "type": "Literal", + "start": 649, + "end": 650, + "loc": { + "start": { + "line": 36, + "column": 18 + }, + "end": { + "line": 36, + "column": 19 + } + }, + "value": 1, + "raw": "1" + } + }, + "consequent": { + "type": "ReturnStatement", + "start": 652, + "end": 663, + "loc": { + "start": { + "line": 36, + "column": 21 + }, + "end": { + "line": 36, + "column": 32 + } + }, + "argument": { + "type": "Literal", + "start": 659, + "end": 662, + "loc": { + "start": { + "line": 36, + "column": 28 + }, + "end": { + "line": 36, + "column": 31 + } + }, + "value": "x", + "raw": "'x'" + } + }, + "alternate": null, + "leadingComments": [ + { + "type": "Line", + "value": " A reserved word used as a property name is an operand too", + "start": 570, + "end": 630 + } + ] + }, + { + "type": "ReturnStatement", + "start": 678, + "end": 690, + "loc": { + "start": { + "line": 39, + "column": 2 + }, + "end": { + "line": 39, + "column": 14 + } + }, + "argument": { + "type": "Literal", + "start": 685, + "end": 689, + "loc": { + "start": { + "line": 39, + "column": 9 + }, + "end": { + "line": 39, + "column": 13 + } + }, + "value": null, + "raw": "null" + }, + "leadingComments": [ + { + "type": "Block", + "value": " c5 ", + "start": 667, + "end": 675 + } + ] + } + ] + } + } + ], + "optional": false + } + } + ], + "sourceType": "module" + }, + "attributes": [ + { + "type": "Attribute", + "start": 8, + "end": 17, + "name": "lang", + "name_loc": { + "start": { + "line": 1, + "column": 8, + "character": 8 + }, + "end": { + "line": 1, + "column": 12, + "character": 12 + } + }, + "value": [ + { + "start": 14, + "end": 16, + "type": "Text", + "raw": "ts", + "data": "ts" + } + ] + } + ] + } +} diff --git a/tests/fixtures/typescript/expressions/literals/regex/keyword_operand_slash/input.svelte b/tests/fixtures/typescript/expressions/literals/regex/keyword_operand_slash/input.svelte new file mode 100644 index 000000000..9ac6e8b37 --- /dev/null +++ b/tests/fixtures/typescript/expressions/literals/regex/keyword_operand_slash/input.svelte @@ -0,0 +1,41 @@ + diff --git a/tests/fixtures/typescript/expressions/literals/regex/update_operand_slash/expected.json b/tests/fixtures/typescript/expressions/literals/regex/update_operand_slash/expected.json new file mode 100644 index 000000000..ade4bcd20 --- /dev/null +++ b/tests/fixtures/typescript/expressions/literals/regex/update_operand_slash/expected.json @@ -0,0 +1,1144 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 529, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " A postfix `++` ends an operand, so the `/` divides and the comment stays inside the parens", + "start": 20, + "end": 113, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 94 + } + } + }, + { + "type": "Block", + "value": " c1 ", + "start": 133, + "end": 141, + "loc": { + "start": { + "line": 3, + "column": 19 + }, + "end": { + "line": 3, + "column": 27 + } + } + }, + { + "type": "Line", + "value": " Same for postfix `--`", + "start": 157, + "end": 181, + "loc": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 7, + "column": 25 + } + } + }, + { + "type": "Block", + "value": " c2 ", + "start": 201, + "end": 209, + "loc": { + "start": { + "line": 8, + "column": 19 + }, + "end": { + "line": 8, + "column": 27 + } + } + }, + { + "type": "Line", + "value": " Another paren-scanned head", + "start": 225, + "end": 254, + "loc": { + "start": { + "line": 12, + "column": 1 + }, + "end": { + "line": 12, + "column": 30 + } + } + }, + { + "type": "Block", + "value": " c3 ", + "start": 274, + "end": 282, + "loc": { + "start": { + "line": 13, + "column": 19 + }, + "end": { + "line": 13, + "column": 27 + } + } + }, + { + "type": "Line", + "value": " Plain division — the operand is an identifier", + "start": 312, + "end": 360, + "loc": { + "start": { + "line": 18, + "column": 1 + }, + "end": { + "line": 18, + "column": 49 + } + } + }, + { + "type": "Block", + "value": " c4 ", + "start": 378, + "end": 386, + "loc": { + "start": { + "line": 19, + "column": 17 + }, + "end": { + "line": 19, + "column": 25 + } + } + }, + { + "type": "Line", + "value": " A binary `+` does NOT end an operand, so the `/` still opens a regex", + "start": 402, + "end": 473, + "loc": { + "start": { + "line": 23, + "column": 1 + }, + "end": { + "line": 23, + "column": 72 + } + } + }, + { + "type": "Block", + "value": " c5 ", + "start": 497, + "end": 505, + "loc": { + "start": { + "line": 24, + "column": 23 + }, + "end": { + "line": 24, + "column": 31 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 528, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 519, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 27, + "column": 9 + } + }, + "body": [ + { + "type": "IfStatement", + "start": 115, + "end": 154, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 5, + "column": 2 + } + }, + "test": { + "type": "BinaryExpression", + "start": 119, + "end": 132, + "loc": { + "start": { + "line": 3, + "column": 5 + }, + "end": { + "line": 3, + "column": 18 + } + }, + "left": { + "type": "BinaryExpression", + "start": 119, + "end": 128, + "loc": { + "start": { + "line": 3, + "column": 5 + }, + "end": { + "line": 3, + "column": 14 + } + }, + "left": { + "type": "UpdateExpression", + "start": 119, + "end": 123, + "loc": { + "start": { + "line": 3, + "column": 5 + }, + "end": { + "line": 3, + "column": 9 + } + }, + "operator": "++", + "prefix": false, + "argument": { + "type": "Identifier", + "start": 119, + "end": 121, + "loc": { + "start": { + "line": 3, + "column": 5 + }, + "end": { + "line": 3, + "column": 7 + } + }, + "name": "aa" + } + }, + "operator": "/", + "right": { + "type": "Identifier", + "start": 126, + "end": 128, + "loc": { + "start": { + "line": 3, + "column": 12 + }, + "end": { + "line": 3, + "column": 14 + } + }, + "name": "bb" + } + }, + "operator": ">", + "right": { + "type": "Literal", + "start": 131, + "end": 132, + "loc": { + "start": { + "line": 3, + "column": 17 + }, + "end": { + "line": 3, + "column": 18 + } + }, + "value": 1, + "raw": "1" + }, + "trailingComments": [ + { + "type": "Block", + "value": " c1 ", + "start": 133, + "end": 141 + } + ] + }, + "consequent": { + "type": "BlockStatement", + "start": 143, + "end": 154, + "loc": { + "start": { + "line": 3, + "column": 29 + }, + "end": { + "line": 5, + "column": 2 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 147, + "end": 151, + "loc": { + "start": { + "line": 4, + "column": 2 + }, + "end": { + "line": 4, + "column": 6 + } + }, + "expression": { + "type": "CallExpression", + "start": 147, + "end": 150, + "loc": { + "start": { + "line": 4, + "column": 2 + }, + "end": { + "line": 4, + "column": 5 + } + }, + "callee": { + "type": "Identifier", + "start": 147, + "end": 148, + "loc": { + "start": { + "line": 4, + "column": 2 + }, + "end": { + "line": 4, + "column": 3 + } + }, + "name": "g" + }, + "arguments": [], + "optional": false + } + } + ] + }, + "alternate": null, + "leadingComments": [ + { + "type": "Line", + "value": " A postfix `++` ends an operand, so the `/` divides and the comment stays inside the parens", + "start": 20, + "end": 113 + } + ] + }, + { + "type": "IfStatement", + "start": 183, + "end": 222, + "loc": { + "start": { + "line": 8, + "column": 1 + }, + "end": { + "line": 10, + "column": 2 + } + }, + "test": { + "type": "BinaryExpression", + "start": 187, + "end": 200, + "loc": { + "start": { + "line": 8, + "column": 5 + }, + "end": { + "line": 8, + "column": 18 + } + }, + "left": { + "type": "BinaryExpression", + "start": 187, + "end": 196, + "loc": { + "start": { + "line": 8, + "column": 5 + }, + "end": { + "line": 8, + "column": 14 + } + }, + "left": { + "type": "UpdateExpression", + "start": 187, + "end": 191, + "loc": { + "start": { + "line": 8, + "column": 5 + }, + "end": { + "line": 8, + "column": 9 + } + }, + "operator": "--", + "prefix": false, + "argument": { + "type": "Identifier", + "start": 187, + "end": 189, + "loc": { + "start": { + "line": 8, + "column": 5 + }, + "end": { + "line": 8, + "column": 7 + } + }, + "name": "aa" + } + }, + "operator": "/", + "right": { + "type": "Identifier", + "start": 194, + "end": 196, + "loc": { + "start": { + "line": 8, + "column": 12 + }, + "end": { + "line": 8, + "column": 14 + } + }, + "name": "bb" + } + }, + "operator": ">", + "right": { + "type": "Literal", + "start": 199, + "end": 200, + "loc": { + "start": { + "line": 8, + "column": 17 + }, + "end": { + "line": 8, + "column": 18 + } + }, + "value": 1, + "raw": "1" + }, + "trailingComments": [ + { + "type": "Block", + "value": " c2 ", + "start": 201, + "end": 209 + } + ] + }, + "consequent": { + "type": "BlockStatement", + "start": 211, + "end": 222, + "loc": { + "start": { + "line": 8, + "column": 29 + }, + "end": { + "line": 10, + "column": 2 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 215, + "end": 219, + "loc": { + "start": { + "line": 9, + "column": 2 + }, + "end": { + "line": 9, + "column": 6 + } + }, + "expression": { + "type": "CallExpression", + "start": 215, + "end": 218, + "loc": { + "start": { + "line": 9, + "column": 2 + }, + "end": { + "line": 9, + "column": 5 + } + }, + "callee": { + "type": "Identifier", + "start": 215, + "end": 216, + "loc": { + "start": { + "line": 9, + "column": 2 + }, + "end": { + "line": 9, + "column": 3 + } + }, + "name": "g" + }, + "arguments": [], + "optional": false + } + } + ] + }, + "alternate": null, + "leadingComments": [ + { + "type": "Line", + "value": " Same for postfix `--`", + "start": 157, + "end": 181 + } + ] + }, + { + "type": "SwitchStatement", + "start": 256, + "end": 309, + "loc": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 16, + "column": 2 + } + }, + "discriminant": { + "type": "BinaryExpression", + "start": 264, + "end": 273, + "loc": { + "start": { + "line": 13, + "column": 9 + }, + "end": { + "line": 13, + "column": 18 + } + }, + "left": { + "type": "UpdateExpression", + "start": 264, + "end": 268, + "loc": { + "start": { + "line": 13, + "column": 9 + }, + "end": { + "line": 13, + "column": 13 + } + }, + "operator": "++", + "prefix": false, + "argument": { + "type": "Identifier", + "start": 264, + "end": 266, + "loc": { + "start": { + "line": 13, + "column": 9 + }, + "end": { + "line": 13, + "column": 11 + } + }, + "name": "aa" + } + }, + "operator": "/", + "right": { + "type": "Identifier", + "start": 271, + "end": 273, + "loc": { + "start": { + "line": 13, + "column": 16 + }, + "end": { + "line": 13, + "column": 18 + } + }, + "name": "bb" + }, + "trailingComments": [ + { + "type": "Block", + "value": " c3 ", + "start": 274, + "end": 282 + } + ] + }, + "cases": [ + { + "type": "SwitchCase", + "start": 288, + "end": 306, + "loc": { + "start": { + "line": 14, + "column": 2 + }, + "end": { + "line": 15, + "column": 9 + } + }, + "consequent": [ + { + "type": "BreakStatement", + "start": 300, + "end": 306, + "loc": { + "start": { + "line": 15, + "column": 3 + }, + "end": { + "line": 15, + "column": 9 + } + }, + "label": null + } + ], + "test": null + } + ], + "leadingComments": [ + { + "type": "Line", + "value": " Another paren-scanned head", + "start": 225, + "end": 254 + } + ] + }, + { + "type": "IfStatement", + "start": 362, + "end": 399, + "loc": { + "start": { + "line": 19, + "column": 1 + }, + "end": { + "line": 21, + "column": 2 + } + }, + "test": { + "type": "BinaryExpression", + "start": 366, + "end": 377, + "loc": { + "start": { + "line": 19, + "column": 5 + }, + "end": { + "line": 19, + "column": 16 + } + }, + "left": { + "type": "BinaryExpression", + "start": 366, + "end": 373, + "loc": { + "start": { + "line": 19, + "column": 5 + }, + "end": { + "line": 19, + "column": 12 + } + }, + "left": { + "type": "Identifier", + "start": 366, + "end": 368, + "loc": { + "start": { + "line": 19, + "column": 5 + }, + "end": { + "line": 19, + "column": 7 + } + }, + "name": "aa" + }, + "operator": "/", + "right": { + "type": "Identifier", + "start": 371, + "end": 373, + "loc": { + "start": { + "line": 19, + "column": 10 + }, + "end": { + "line": 19, + "column": 12 + } + }, + "name": "bb" + } + }, + "operator": ">", + "right": { + "type": "Literal", + "start": 376, + "end": 377, + "loc": { + "start": { + "line": 19, + "column": 15 + }, + "end": { + "line": 19, + "column": 16 + } + }, + "value": 1, + "raw": "1" + }, + "trailingComments": [ + { + "type": "Block", + "value": " c4 ", + "start": 378, + "end": 386 + } + ] + }, + "consequent": { + "type": "BlockStatement", + "start": 388, + "end": 399, + "loc": { + "start": { + "line": 19, + "column": 27 + }, + "end": { + "line": 21, + "column": 2 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 392, + "end": 396, + "loc": { + "start": { + "line": 20, + "column": 2 + }, + "end": { + "line": 20, + "column": 6 + } + }, + "expression": { + "type": "CallExpression", + "start": 392, + "end": 395, + "loc": { + "start": { + "line": 20, + "column": 2 + }, + "end": { + "line": 20, + "column": 5 + } + }, + "callee": { + "type": "Identifier", + "start": 392, + "end": 393, + "loc": { + "start": { + "line": 20, + "column": 2 + }, + "end": { + "line": 20, + "column": 3 + } + }, + "name": "g" + }, + "arguments": [], + "optional": false + } + } + ] + }, + "alternate": null, + "leadingComments": [ + { + "type": "Line", + "value": " Plain division — the operand is an identifier", + "start": 312, + "end": 360 + } + ] + }, + { + "type": "IfStatement", + "start": 475, + "end": 518, + "loc": { + "start": { + "line": 24, + "column": 1 + }, + "end": { + "line": 26, + "column": 2 + } + }, + "test": { + "type": "BinaryExpression", + "start": 479, + "end": 496, + "loc": { + "start": { + "line": 24, + "column": 5 + }, + "end": { + "line": 24, + "column": 22 + } + }, + "left": { + "type": "Identifier", + "start": 479, + "end": 481, + "loc": { + "start": { + "line": 24, + "column": 5 + }, + "end": { + "line": 24, + "column": 7 + } + }, + "name": "aa" + }, + "operator": "+", + "right": { + "type": "CallExpression", + "start": 484, + "end": 496, + "loc": { + "start": { + "line": 24, + "column": 10 + }, + "end": { + "line": 24, + "column": 22 + } + }, + "callee": { + "type": "MemberExpression", + "start": 484, + "end": 493, + "loc": { + "start": { + "line": 24, + "column": 10 + }, + "end": { + "line": 24, + "column": 19 + } + }, + "object": { + "type": "Literal", + "start": 484, + "end": 488, + "loc": { + "start": { + "line": 24, + "column": 10 + }, + "end": { + "line": 24, + "column": 14 + } + }, + "value": {}, + "raw": "/re/", + "regex": { + "pattern": "re", + "flags": "" + } + }, + "property": { + "type": "Identifier", + "start": 489, + "end": 493, + "loc": { + "start": { + "line": 24, + "column": 15 + }, + "end": { + "line": 24, + "column": 19 + } + }, + "name": "test" + }, + "computed": false, + "optional": false + }, + "arguments": [ + { + "type": "Identifier", + "start": 494, + "end": 495, + "loc": { + "start": { + "line": 24, + "column": 20 + }, + "end": { + "line": 24, + "column": 21 + } + }, + "name": "b", + "trailingComments": [ + { + "type": "Block", + "value": " c5 ", + "start": 497, + "end": 505 + } + ] + } + ], + "optional": false + } + }, + "consequent": { + "type": "BlockStatement", + "start": 507, + "end": 518, + "loc": { + "start": { + "line": 24, + "column": 33 + }, + "end": { + "line": 26, + "column": 2 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 511, + "end": 515, + "loc": { + "start": { + "line": 25, + "column": 2 + }, + "end": { + "line": 25, + "column": 6 + } + }, + "expression": { + "type": "CallExpression", + "start": 511, + "end": 514, + "loc": { + "start": { + "line": 25, + "column": 2 + }, + "end": { + "line": 25, + "column": 5 + } + }, + "callee": { + "type": "Identifier", + "start": 511, + "end": 512, + "loc": { + "start": { + "line": 25, + "column": 2 + }, + "end": { + "line": 25, + "column": 3 + } + }, + "name": "g" + }, + "arguments": [], + "optional": false + } + } + ] + }, + "alternate": null, + "leadingComments": [ + { + "type": "Line", + "value": " A binary `+` does NOT end an operand, so the `/` still opens a regex", + "start": 402, + "end": 473 + } + ] + } + ], + "sourceType": "module" + }, + "attributes": [ + { + "type": "Attribute", + "start": 8, + "end": 17, + "name": "lang", + "name_loc": { + "start": { + "line": 1, + "column": 8, + "character": 8 + }, + "end": { + "line": 1, + "column": 12, + "character": 12 + } + }, + "value": [ + { + "start": 14, + "end": 16, + "type": "Text", + "raw": "ts", + "data": "ts" + } + ] + } + ] + } +} diff --git a/tests/fixtures/typescript/expressions/literals/regex/update_operand_slash/input.svelte b/tests/fixtures/typescript/expressions/literals/regex/update_operand_slash/input.svelte new file mode 100644 index 000000000..6ab714173 --- /dev/null +++ b/tests/fixtures/typescript/expressions/literals/regex/update_operand_slash/input.svelte @@ -0,0 +1,27 @@ + diff --git a/tests/fixtures/typescript/expressions/member/paren_binary_base_comment/expected.json b/tests/fixtures/typescript/expressions/member/paren_binary_base_comment/expected.json new file mode 100644 index 000000000..f2577b33c --- /dev/null +++ b/tests/fixtures/typescript/expressions/member/paren_binary_base_comment/expected.json @@ -0,0 +1,1353 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 629, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " trailing block comment on the left operand of a parenthesized binary member base", + "start": 10, + "end": 93, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 84 + } + } + }, + { + "type": "Block", + "value": " c ", + "start": 116, + "end": 123, + "loc": { + "start": { + "line": 3, + "column": 22 + }, + "end": { + "line": 3, + "column": 29 + } + } + }, + { + "type": "Block", + "value": " c ", + "start": 163, + "end": 170, + "loc": { + "start": { + "line": 4, + "column": 21 + }, + "end": { + "line": 4, + "column": 28 + } + } + }, + { + "type": "Line", + "value": " leading block comment on the right operand", + "start": 190, + "end": 235, + "loc": { + "start": { + "line": 6, + "column": 1 + }, + "end": { + "line": 6, + "column": 46 + } + } + }, + { + "type": "Block", + "value": " c ", + "start": 261, + "end": 268, + "loc": { + "start": { + "line": 7, + "column": 25 + }, + "end": { + "line": 7, + "column": 32 + } + } + }, + { + "type": "Block", + "value": " c ", + "start": 307, + "end": 314, + "loc": { + "start": { + "line": 8, + "column": 23 + }, + "end": { + "line": 8, + "column": 30 + } + } + }, + { + "type": "Line", + "value": " a line comment before the operator breaks the base onto its own lines", + "start": 332, + "end": 404, + "loc": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 10, + "column": 73 + } + } + }, + { + "type": "Line", + "value": " c", + "start": 432, + "end": 436, + "loc": { + "start": { + "line": 12, + "column": 14 + }, + "end": { + "line": 12, + "column": 18 + } + } + }, + { + "type": "Line", + "value": " the same comment on a base too long to fit", + "start": 458, + "end": 503, + "loc": { + "start": { + "line": 16, + "column": 1 + }, + "end": { + "line": 16, + "column": 46 + } + } + }, + { + "type": "Block", + "value": " c ", + "start": 576, + "end": 583, + "loc": { + "start": { + "line": 18, + "column": 59 + }, + "end": { + "line": 18, + "column": 66 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 628, + "context": "default", + "content": { + "type": "Program", + "start": 8, + "end": 619, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 21, + "column": 9 + } + }, + "body": [ + { + "type": "VariableDeclaration", + "start": 95, + "end": 141, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 47 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 101, + "end": 140, + "loc": { + "start": { + "line": 3, + "column": 7 + }, + "end": { + "line": 3, + "column": 46 + } + }, + "id": { + "type": "Identifier", + "start": 101, + "end": 103, + "loc": { + "start": { + "line": 3, + "column": 7 + }, + "end": { + "line": 3, + "column": 9 + } + }, + "name": "a1" + }, + "init": { + "type": "CallExpression", + "start": 106, + "end": 140, + "loc": { + "start": { + "line": 3, + "column": 12 + }, + "end": { + "line": 3, + "column": 46 + } + }, + "callee": { + "type": "MemberExpression", + "start": 106, + "end": 138, + "loc": { + "start": { + "line": 3, + "column": 12 + }, + "end": { + "line": 3, + "column": 44 + } + }, + "object": { + "type": "LogicalExpression", + "start": 107, + "end": 128, + "loc": { + "start": { + "line": 3, + "column": 13 + }, + "end": { + "line": 3, + "column": 34 + } + }, + "left": { + "type": "LogicalExpression", + "start": 108, + "end": 114, + "loc": { + "start": { + "line": 3, + "column": 14 + }, + "end": { + "line": 3, + "column": 20 + } + }, + "left": { + "type": "Identifier", + "start": 108, + "end": 109, + "loc": { + "start": { + "line": 3, + "column": 14 + }, + "end": { + "line": 3, + "column": 15 + } + }, + "name": "x" + }, + "operator": "&&", + "right": { + "type": "Identifier", + "start": 113, + "end": 114, + "loc": { + "start": { + "line": 3, + "column": 19 + }, + "end": { + "line": 3, + "column": 20 + } + }, + "name": "y" + }, + "trailingComments": [ + { + "type": "Block", + "value": " c ", + "start": 116, + "end": 123 + } + ] + }, + "operator": "||", + "right": { + "type": "Identifier", + "start": 127, + "end": 128, + "loc": { + "start": { + "line": 3, + "column": 33 + }, + "end": { + "line": 3, + "column": 34 + } + }, + "name": "z" + } + }, + "property": { + "type": "Identifier", + "start": 130, + "end": 138, + "loc": { + "start": { + "line": 3, + "column": 36 + }, + "end": { + "line": 3, + "column": 44 + } + }, + "name": "toString" + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " trailing block comment on the left operand of a parenthesized binary member base", + "start": 10, + "end": 93 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 143, + "end": 187, + "loc": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 45 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 149, + "end": 186, + "loc": { + "start": { + "line": 4, + "column": 7 + }, + "end": { + "line": 4, + "column": 44 + } + }, + "id": { + "type": "Identifier", + "start": 149, + "end": 151, + "loc": { + "start": { + "line": 4, + "column": 7 + }, + "end": { + "line": 4, + "column": 9 + } + }, + "name": "a2" + }, + "init": { + "type": "CallExpression", + "start": 154, + "end": 186, + "loc": { + "start": { + "line": 4, + "column": 12 + }, + "end": { + "line": 4, + "column": 44 + } + }, + "callee": { + "type": "MemberExpression", + "start": 154, + "end": 184, + "loc": { + "start": { + "line": 4, + "column": 12 + }, + "end": { + "line": 4, + "column": 42 + } + }, + "object": { + "type": "BinaryExpression", + "start": 155, + "end": 174, + "loc": { + "start": { + "line": 4, + "column": 13 + }, + "end": { + "line": 4, + "column": 32 + } + }, + "left": { + "type": "BinaryExpression", + "start": 156, + "end": 161, + "loc": { + "start": { + "line": 4, + "column": 14 + }, + "end": { + "line": 4, + "column": 19 + } + }, + "left": { + "type": "Identifier", + "start": 156, + "end": 157, + "loc": { + "start": { + "line": 4, + "column": 14 + }, + "end": { + "line": 4, + "column": 15 + } + }, + "name": "x" + }, + "operator": "+", + "right": { + "type": "Identifier", + "start": 160, + "end": 161, + "loc": { + "start": { + "line": 4, + "column": 18 + }, + "end": { + "line": 4, + "column": 19 + } + }, + "name": "y" + }, + "trailingComments": [ + { + "type": "Block", + "value": " c ", + "start": 163, + "end": 170 + } + ] + }, + "operator": "/", + "right": { + "type": "Identifier", + "start": 173, + "end": 174, + "loc": { + "start": { + "line": 4, + "column": 31 + }, + "end": { + "line": 4, + "column": 32 + } + }, + "name": "z" + } + }, + "property": { + "type": "Identifier", + "start": 176, + "end": 184, + "loc": { + "start": { + "line": 4, + "column": 34 + }, + "end": { + "line": 4, + "column": 42 + } + }, + "name": "toString" + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + } + } + ], + "kind": "const" + }, + { + "type": "VariableDeclaration", + "start": 237, + "end": 283, + "loc": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 7, + "column": 47 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 243, + "end": 282, + "loc": { + "start": { + "line": 7, + "column": 7 + }, + "end": { + "line": 7, + "column": 46 + } + }, + "id": { + "type": "Identifier", + "start": 243, + "end": 245, + "loc": { + "start": { + "line": 7, + "column": 7 + }, + "end": { + "line": 7, + "column": 9 + } + }, + "name": "b1" + }, + "init": { + "type": "CallExpression", + "start": 248, + "end": 282, + "loc": { + "start": { + "line": 7, + "column": 12 + }, + "end": { + "line": 7, + "column": 46 + } + }, + "callee": { + "type": "MemberExpression", + "start": 248, + "end": 280, + "loc": { + "start": { + "line": 7, + "column": 12 + }, + "end": { + "line": 7, + "column": 44 + } + }, + "object": { + "type": "LogicalExpression", + "start": 249, + "end": 270, + "loc": { + "start": { + "line": 7, + "column": 13 + }, + "end": { + "line": 7, + "column": 34 + } + }, + "left": { + "type": "LogicalExpression", + "start": 250, + "end": 256, + "loc": { + "start": { + "line": 7, + "column": 14 + }, + "end": { + "line": 7, + "column": 20 + } + }, + "left": { + "type": "Identifier", + "start": 250, + "end": 251, + "loc": { + "start": { + "line": 7, + "column": 14 + }, + "end": { + "line": 7, + "column": 15 + } + }, + "name": "x" + }, + "operator": "&&", + "right": { + "type": "Identifier", + "start": 255, + "end": 256, + "loc": { + "start": { + "line": 7, + "column": 19 + }, + "end": { + "line": 7, + "column": 20 + } + }, + "name": "y" + } + }, + "operator": "||", + "right": { + "type": "Identifier", + "start": 269, + "end": 270, + "loc": { + "start": { + "line": 7, + "column": 33 + }, + "end": { + "line": 7, + "column": 34 + } + }, + "name": "z", + "leadingComments": [ + { + "type": "Block", + "value": " c ", + "start": 261, + "end": 268 + } + ] + } + }, + "property": { + "type": "Identifier", + "start": 272, + "end": 280, + "loc": { + "start": { + "line": 7, + "column": 36 + }, + "end": { + "line": 7, + "column": 44 + } + }, + "name": "toString" + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " leading block comment on the right operand", + "start": 190, + "end": 235 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 285, + "end": 329, + "loc": { + "start": { + "line": 8, + "column": 1 + }, + "end": { + "line": 8, + "column": 45 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 291, + "end": 328, + "loc": { + "start": { + "line": 8, + "column": 7 + }, + "end": { + "line": 8, + "column": 44 + } + }, + "id": { + "type": "Identifier", + "start": 291, + "end": 293, + "loc": { + "start": { + "line": 8, + "column": 7 + }, + "end": { + "line": 8, + "column": 9 + } + }, + "name": "b2" + }, + "init": { + "type": "CallExpression", + "start": 296, + "end": 328, + "loc": { + "start": { + "line": 8, + "column": 12 + }, + "end": { + "line": 8, + "column": 44 + } + }, + "callee": { + "type": "MemberExpression", + "start": 296, + "end": 326, + "loc": { + "start": { + "line": 8, + "column": 12 + }, + "end": { + "line": 8, + "column": 42 + } + }, + "object": { + "type": "BinaryExpression", + "start": 297, + "end": 316, + "loc": { + "start": { + "line": 8, + "column": 13 + }, + "end": { + "line": 8, + "column": 32 + } + }, + "left": { + "type": "BinaryExpression", + "start": 298, + "end": 303, + "loc": { + "start": { + "line": 8, + "column": 14 + }, + "end": { + "line": 8, + "column": 19 + } + }, + "left": { + "type": "Identifier", + "start": 298, + "end": 299, + "loc": { + "start": { + "line": 8, + "column": 14 + }, + "end": { + "line": 8, + "column": 15 + } + }, + "name": "x" + }, + "operator": "+", + "right": { + "type": "Identifier", + "start": 302, + "end": 303, + "loc": { + "start": { + "line": 8, + "column": 18 + }, + "end": { + "line": 8, + "column": 19 + } + }, + "name": "y" + } + }, + "operator": "/", + "right": { + "type": "Identifier", + "start": 315, + "end": 316, + "loc": { + "start": { + "line": 8, + "column": 31 + }, + "end": { + "line": 8, + "column": 32 + } + }, + "name": "z", + "leadingComments": [ + { + "type": "Block", + "value": " c ", + "start": 307, + "end": 314 + } + ] + } + }, + "property": { + "type": "Identifier", + "start": 318, + "end": 326, + "loc": { + "start": { + "line": 8, + "column": 34 + }, + "end": { + "line": 8, + "column": 42 + } + }, + "name": "toString" + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + } + } + ], + "kind": "const" + }, + { + "type": "VariableDeclaration", + "start": 406, + "end": 455, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 14, + "column": 14 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 412, + "end": 454, + "loc": { + "start": { + "line": 11, + "column": 7 + }, + "end": { + "line": 14, + "column": 13 + } + }, + "id": { + "type": "Identifier", + "start": 412, + "end": 413, + "loc": { + "start": { + "line": 11, + "column": 7 + }, + "end": { + "line": 11, + "column": 8 + } + }, + "name": "c" + }, + "init": { + "type": "CallExpression", + "start": 416, + "end": 454, + "loc": { + "start": { + "line": 11, + "column": 11 + }, + "end": { + "line": 14, + "column": 13 + } + }, + "callee": { + "type": "MemberExpression", + "start": 416, + "end": 452, + "loc": { + "start": { + "line": 11, + "column": 11 + }, + "end": { + "line": 14, + "column": 11 + } + }, + "object": { + "type": "LogicalExpression", + "start": 420, + "end": 440, + "loc": { + "start": { + "line": 12, + "column": 2 + }, + "end": { + "line": 13, + "column": 3 + } + }, + "left": { + "type": "LogicalExpression", + "start": 421, + "end": 427, + "loc": { + "start": { + "line": 12, + "column": 3 + }, + "end": { + "line": 12, + "column": 9 + } + }, + "left": { + "type": "Identifier", + "start": 421, + "end": 422, + "loc": { + "start": { + "line": 12, + "column": 3 + }, + "end": { + "line": 12, + "column": 4 + } + }, + "name": "x" + }, + "operator": "&&", + "right": { + "type": "Identifier", + "start": 426, + "end": 427, + "loc": { + "start": { + "line": 12, + "column": 8 + }, + "end": { + "line": 12, + "column": 9 + } + }, + "name": "y" + } + }, + "operator": "||", + "right": { + "type": "Identifier", + "start": 439, + "end": 440, + "loc": { + "start": { + "line": 13, + "column": 2 + }, + "end": { + "line": 13, + "column": 3 + } + }, + "name": "z", + "leadingComments": [ + { + "type": "Line", + "value": " c", + "start": 432, + "end": 436 + } + ] + } + }, + "property": { + "type": "Identifier", + "start": 444, + "end": 452, + "loc": { + "start": { + "line": 14, + "column": 3 + }, + "end": { + "line": 14, + "column": 11 + } + }, + "name": "toString" + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " a line comment before the operator breaks the base onto its own lines", + "start": 332, + "end": 404 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 505, + "end": 618, + "loc": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 20, + "column": 14 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 511, + "end": 617, + "loc": { + "start": { + "line": 17, + "column": 7 + }, + "end": { + "line": 20, + "column": 13 + } + }, + "id": { + "type": "Identifier", + "start": 511, + "end": 512, + "loc": { + "start": { + "line": 17, + "column": 7 + }, + "end": { + "line": 17, + "column": 8 + } + }, + "name": "d" + }, + "init": { + "type": "CallExpression", + "start": 515, + "end": 617, + "loc": { + "start": { + "line": 17, + "column": 11 + }, + "end": { + "line": 20, + "column": 13 + } + }, + "callee": { + "type": "MemberExpression", + "start": 515, + "end": 615, + "loc": { + "start": { + "line": 17, + "column": 11 + }, + "end": { + "line": 20, + "column": 11 + } + }, + "object": { + "type": "LogicalExpression", + "start": 519, + "end": 603, + "loc": { + "start": { + "line": 18, + "column": 2 + }, + "end": { + "line": 19, + "column": 16 + } + }, + "left": { + "type": "LogicalExpression", + "start": 520, + "end": 574, + "loc": { + "start": { + "line": 18, + "column": 3 + }, + "end": { + "line": 18, + "column": 57 + } + }, + "left": { + "type": "Identifier", + "start": 520, + "end": 545, + "loc": { + "start": { + "line": 18, + "column": 3 + }, + "end": { + "line": 18, + "column": 28 + } + }, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "operator": "&&", + "right": { + "type": "Identifier", + "start": 549, + "end": 574, + "loc": { + "start": { + "line": 18, + "column": 32 + }, + "end": { + "line": 18, + "column": 57 + } + }, + "name": "bbbbbbbbbbbbbbbbbbbbbbbbb" + }, + "trailingComments": [ + { + "type": "Block", + "value": " c ", + "start": 576, + "end": 583 + } + ] + }, + "operator": "||", + "right": { + "type": "Identifier", + "start": 589, + "end": 603, + "loc": { + "start": { + "line": 19, + "column": 2 + }, + "end": { + "line": 19, + "column": 16 + } + }, + "name": "zzzzzzzzzzzzzz" + } + }, + "property": { + "type": "Identifier", + "start": 607, + "end": 615, + "loc": { + "start": { + "line": 20, + "column": 3 + }, + "end": { + "line": 20, + "column": 11 + } + }, + "name": "toString" + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " the same comment on a base too long to fit", + "start": 458, + "end": 503 + } + ] + } + ], + "sourceType": "module" + }, + "attributes": [] + } +} diff --git a/tests/fixtures/typescript/expressions/member/paren_binary_base_comment/input.svelte b/tests/fixtures/typescript/expressions/member/paren_binary_base_comment/input.svelte new file mode 100644 index 000000000..3796f560f --- /dev/null +++ b/tests/fixtures/typescript/expressions/member/paren_binary_base_comment/input.svelte @@ -0,0 +1,21 @@ + diff --git a/tests/fixtures/typescript/expressions/member/paren_binary_base_comment/unformatted_compact.svelte b/tests/fixtures/typescript/expressions/member/paren_binary_base_comment/unformatted_compact.svelte new file mode 100644 index 000000000..cf786cb7c --- /dev/null +++ b/tests/fixtures/typescript/expressions/member/paren_binary_base_comment/unformatted_compact.svelte @@ -0,0 +1,16 @@ + diff --git a/tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/README.md b/tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/README.md new file mode 100644 index 000000000..a7251b0e3 --- /dev/null +++ b/tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/README.md @@ -0,0 +1,50 @@ +# paren_binary_base_long_prettier_divergence + +A parenthesized binary expression used as a member-access base, long enough that the parens must +break onto their own lines, whose left operand is itself a parenthesized binary. + +tsv: breaks the parens and holds the operand chain together while it fits (`(\n\t(a && b) || c\n).toString()`) +Prettier: breaks the parens **and** splits the operand chain onto separate lines + +| Case | tsv | Prettier | +| --------------------------------------- | ------------------ | -------------------- | +| `((a && b) \|\| c).toString()` at 100 | inline | inline | +| `((a && b) \|\| c).toString()` at 101 | operand chain flat | one operand per line | +| `((a + b) / c).toString()` | operand chain flat | one operand per line | +| `((a && b) ?? c).toString()` | operand chain flat | one operand per line | +| `((a && b) \|\| c)!.toString()` | parens hang | welds `)!.toString()` onto the last operand | +| `((a && b) \|\| c)?.toString()` | operand chain flat | one operand per line | +| `(a && b && c).toString()` (flat chain) | one operand per line | one operand per line | + +## Reason + +Design choice, and minimal breaking. Breaking the parens is what the width demands; splitting the +operand chain as well is a second break the width does not ask for. tsv takes only the break it +needs, so the chain still reads as one expression. + +The rule is uniform along both axes Prettier varies on: + +- **Operator family** — arithmetic, logical (`&&` / `||`), and nullish (`??`) bases take the + identical shape. The alternative — a third layout for logical and nullish bases that welds the + closing `).member` onto the last operand (`((a && b) ||\n\tc).toString()`) — matches neither tsv's + own arithmetic shape nor Prettier's, so it is a shape with no constituency. +- **What follows the base** — a plain `.member`, a non-null `!.member`, and an optional `?.member` + all lay out identically. Prettier varies: it hangs the parens for `.` and `?.` but welds + `)!.member` onto the last operand. This is the same stance as + `non_null_paren_base_long_prettier_divergence`, extended from call/await bases to binary ones. + +A **computed** lookup (`(…)[k]`) is deliberately not covered here: it is governed by the separate +never-break-before-`[` rule (`member/computed_paren_base_long/`), which breaks the brackets before +the parens ever hang, so it would pin two rules' interaction rather than this one. + +A **flat** chain (`a && b && c`, no parenthesized operand) is not part of the divergence — there is +no nested operand to hold together, so both formatters break every operand. It is pinned here as the +boundary of the rule. + +## Related + +- `member/paren_base_trailing_long/` — a parenthesized **call** or `as`-cast base, where tsv matches Prettier. +- `member/non_null_paren_base_long_prettier_divergence/` — the same "layout is independent of `!`" stance for call/await bases. +- `typescript_specific/non_null/long/` — the other non-null layouts, none of which diverge. + +See [conformance_prettier.md](../../../../../../docs/conformance_prettier.md) §TypeScript (Parenthesized binary member base) and [§Print Width Philosophy](../../../../../../docs/conformance_prettier.md#print-width-philosophy). diff --git a/tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/expected.json b/tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/expected.json new file mode 100644 index 000000000..d41addefd --- /dev/null +++ b/tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/expected.json @@ -0,0 +1,1516 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 1201, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [] + }, + "options": null, + "comments": [ + { + "type": "Line", + "value": " 100 - the whole expression fits, so nothing breaks", + "start": 20, + "end": 73, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 54 + } + } + }, + { + "type": "Line", + "value": " 101 - the parens break onto their own lines; the operand chain stays flat", + "start": 176, + "end": 252, + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 77 + } + } + }, + { + "type": "Line", + "value": " an arithmetic base takes the same shape", + "start": 361, + "end": 403, + "loc": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 10, + "column": 43 + } + } + }, + { + "type": "Line", + "value": " so does a base mixing `??` with `&&`", + "start": 512, + "end": 551, + "loc": { + "start": { + "line": 15, + "column": 1 + }, + "end": { + "line": 15, + "column": 40 + } + } + }, + { + "type": "Line", + "value": " the shape does not change with what follows the base - a non-null", + "start": 660, + "end": 728, + "loc": { + "start": { + "line": 20, + "column": 1 + }, + "end": { + "line": 20, + "column": 69 + } + } + }, + { + "type": "Line", + "value": " assertion and an optional access lay out identically", + "start": 730, + "end": 785, + "loc": { + "start": { + "line": 21, + "column": 1 + }, + "end": { + "line": 21, + "column": 56 + } + } + }, + { + "type": "Line", + "value": " a flat chain has no nested operand to hold together, so every operand breaks", + "start": 1001, + "end": 1080, + "loc": { + "start": { + "line": 30, + "column": 1 + }, + "end": { + "line": 30, + "column": 80 + } + } + } + ], + "instance": { + "type": "Script", + "start": 0, + "end": 1200, + "context": "default", + "content": { + "type": "Program", + "start": 18, + "end": 1191, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 36, + "column": 9 + } + }, + "body": [ + { + "type": "VariableDeclaration", + "start": 75, + "end": 173, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 99 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 81, + "end": 172, + "loc": { + "start": { + "line": 3, + "column": 7 + }, + "end": { + "line": 3, + "column": 98 + } + }, + "id": { + "type": "Identifier", + "start": 81, + "end": 82, + "loc": { + "start": { + "line": 3, + "column": 7 + }, + "end": { + "line": 3, + "column": 8 + } + }, + "name": "a" + }, + "init": { + "type": "CallExpression", + "start": 85, + "end": 172, + "loc": { + "start": { + "line": 3, + "column": 11 + }, + "end": { + "line": 3, + "column": 98 + } + }, + "callee": { + "type": "MemberExpression", + "start": 85, + "end": 170, + "loc": { + "start": { + "line": 3, + "column": 11 + }, + "end": { + "line": 3, + "column": 96 + } + }, + "object": { + "type": "LogicalExpression", + "start": 86, + "end": 160, + "loc": { + "start": { + "line": 3, + "column": 12 + }, + "end": { + "line": 3, + "column": 86 + } + }, + "left": { + "type": "LogicalExpression", + "start": 87, + "end": 141, + "loc": { + "start": { + "line": 3, + "column": 13 + }, + "end": { + "line": 3, + "column": 67 + } + }, + "left": { + "type": "Identifier", + "start": 87, + "end": 112, + "loc": { + "start": { + "line": 3, + "column": 13 + }, + "end": { + "line": 3, + "column": 38 + } + }, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "operator": "&&", + "right": { + "type": "Identifier", + "start": 116, + "end": 141, + "loc": { + "start": { + "line": 3, + "column": 42 + }, + "end": { + "line": 3, + "column": 67 + } + }, + "name": "bbbbbbbbbbbbbbbbbbbbbbbbb" + } + }, + "operator": "||", + "right": { + "type": "Identifier", + "start": 146, + "end": 160, + "loc": { + "start": { + "line": 3, + "column": 72 + }, + "end": { + "line": 3, + "column": 86 + } + }, + "name": "cccccccccccccc" + } + }, + "property": { + "type": "Identifier", + "start": 162, + "end": 170, + "loc": { + "start": { + "line": 3, + "column": 88 + }, + "end": { + "line": 3, + "column": 96 + } + }, + "name": "toString" + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " 100 - the whole expression fits, so nothing breaks", + "start": 20, + "end": 73 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 254, + "end": 358, + "loc": { + "start": { + "line": 6, + "column": 1 + }, + "end": { + "line": 8, + "column": 14 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 260, + "end": 357, + "loc": { + "start": { + "line": 6, + "column": 7 + }, + "end": { + "line": 8, + "column": 13 + } + }, + "id": { + "type": "Identifier", + "start": 260, + "end": 261, + "loc": { + "start": { + "line": 6, + "column": 7 + }, + "end": { + "line": 6, + "column": 8 + } + }, + "name": "b" + }, + "init": { + "type": "CallExpression", + "start": 264, + "end": 357, + "loc": { + "start": { + "line": 6, + "column": 11 + }, + "end": { + "line": 8, + "column": 13 + } + }, + "callee": { + "type": "MemberExpression", + "start": 264, + "end": 355, + "loc": { + "start": { + "line": 6, + "column": 11 + }, + "end": { + "line": 8, + "column": 11 + } + }, + "object": { + "type": "LogicalExpression", + "start": 268, + "end": 343, + "loc": { + "start": { + "line": 7, + "column": 2 + }, + "end": { + "line": 7, + "column": 77 + } + }, + "left": { + "type": "LogicalExpression", + "start": 269, + "end": 323, + "loc": { + "start": { + "line": 7, + "column": 3 + }, + "end": { + "line": 7, + "column": 57 + } + }, + "left": { + "type": "Identifier", + "start": 269, + "end": 294, + "loc": { + "start": { + "line": 7, + "column": 3 + }, + "end": { + "line": 7, + "column": 28 + } + }, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "operator": "&&", + "right": { + "type": "Identifier", + "start": 298, + "end": 323, + "loc": { + "start": { + "line": 7, + "column": 32 + }, + "end": { + "line": 7, + "column": 57 + } + }, + "name": "bbbbbbbbbbbbbbbbbbbbbbbbb" + } + }, + "operator": "||", + "right": { + "type": "Identifier", + "start": 328, + "end": 343, + "loc": { + "start": { + "line": 7, + "column": 62 + }, + "end": { + "line": 7, + "column": 77 + } + }, + "name": "ccccccccccccccc" + } + }, + "property": { + "type": "Identifier", + "start": 347, + "end": 355, + "loc": { + "start": { + "line": 8, + "column": 3 + }, + "end": { + "line": 8, + "column": 11 + } + }, + "name": "toString" + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " 101 - the parens break onto their own lines; the operand chain stays flat", + "start": 176, + "end": 252 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 405, + "end": 509, + "loc": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 13, + "column": 14 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 411, + "end": 508, + "loc": { + "start": { + "line": 11, + "column": 7 + }, + "end": { + "line": 13, + "column": 13 + } + }, + "id": { + "type": "Identifier", + "start": 411, + "end": 412, + "loc": { + "start": { + "line": 11, + "column": 7 + }, + "end": { + "line": 11, + "column": 8 + } + }, + "name": "c" + }, + "init": { + "type": "CallExpression", + "start": 415, + "end": 508, + "loc": { + "start": { + "line": 11, + "column": 11 + }, + "end": { + "line": 13, + "column": 13 + } + }, + "callee": { + "type": "MemberExpression", + "start": 415, + "end": 506, + "loc": { + "start": { + "line": 11, + "column": 11 + }, + "end": { + "line": 13, + "column": 11 + } + }, + "object": { + "type": "BinaryExpression", + "start": 419, + "end": 494, + "loc": { + "start": { + "line": 12, + "column": 2 + }, + "end": { + "line": 12, + "column": 77 + } + }, + "left": { + "type": "BinaryExpression", + "start": 420, + "end": 473, + "loc": { + "start": { + "line": 12, + "column": 3 + }, + "end": { + "line": 12, + "column": 56 + } + }, + "left": { + "type": "Identifier", + "start": 420, + "end": 445, + "loc": { + "start": { + "line": 12, + "column": 3 + }, + "end": { + "line": 12, + "column": 28 + } + }, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "operator": "+", + "right": { + "type": "Identifier", + "start": 448, + "end": 473, + "loc": { + "start": { + "line": 12, + "column": 31 + }, + "end": { + "line": 12, + "column": 56 + } + }, + "name": "bbbbbbbbbbbbbbbbbbbbbbbbb" + } + }, + "operator": "/", + "right": { + "type": "Identifier", + "start": 477, + "end": 494, + "loc": { + "start": { + "line": 12, + "column": 60 + }, + "end": { + "line": 12, + "column": 77 + } + }, + "name": "ccccccccccccccccc" + } + }, + "property": { + "type": "Identifier", + "start": 498, + "end": 506, + "loc": { + "start": { + "line": 13, + "column": 3 + }, + "end": { + "line": 13, + "column": 11 + } + }, + "name": "toString" + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " an arithmetic base takes the same shape", + "start": 361, + "end": 403 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 553, + "end": 657, + "loc": { + "start": { + "line": 16, + "column": 1 + }, + "end": { + "line": 18, + "column": 14 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 559, + "end": 656, + "loc": { + "start": { + "line": 16, + "column": 7 + }, + "end": { + "line": 18, + "column": 13 + } + }, + "id": { + "type": "Identifier", + "start": 559, + "end": 560, + "loc": { + "start": { + "line": 16, + "column": 7 + }, + "end": { + "line": 16, + "column": 8 + } + }, + "name": "d" + }, + "init": { + "type": "CallExpression", + "start": 563, + "end": 656, + "loc": { + "start": { + "line": 16, + "column": 11 + }, + "end": { + "line": 18, + "column": 13 + } + }, + "callee": { + "type": "MemberExpression", + "start": 563, + "end": 654, + "loc": { + "start": { + "line": 16, + "column": 11 + }, + "end": { + "line": 18, + "column": 11 + } + }, + "object": { + "type": "LogicalExpression", + "start": 567, + "end": 642, + "loc": { + "start": { + "line": 17, + "column": 2 + }, + "end": { + "line": 17, + "column": 77 + } + }, + "left": { + "type": "LogicalExpression", + "start": 568, + "end": 622, + "loc": { + "start": { + "line": 17, + "column": 3 + }, + "end": { + "line": 17, + "column": 57 + } + }, + "left": { + "type": "Identifier", + "start": 568, + "end": 593, + "loc": { + "start": { + "line": 17, + "column": 3 + }, + "end": { + "line": 17, + "column": 28 + } + }, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "operator": "&&", + "right": { + "type": "Identifier", + "start": 597, + "end": 622, + "loc": { + "start": { + "line": 17, + "column": 32 + }, + "end": { + "line": 17, + "column": 57 + } + }, + "name": "bbbbbbbbbbbbbbbbbbbbbbbbb" + } + }, + "operator": "??", + "right": { + "type": "Identifier", + "start": 627, + "end": 642, + "loc": { + "start": { + "line": 17, + "column": 62 + }, + "end": { + "line": 17, + "column": 77 + } + }, + "name": "ccccccccccccccc" + } + }, + "property": { + "type": "Identifier", + "start": 646, + "end": 654, + "loc": { + "start": { + "line": 18, + "column": 3 + }, + "end": { + "line": 18, + "column": 11 + } + }, + "name": "toString" + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " so does a base mixing `??` with `&&`", + "start": 512, + "end": 551 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 787, + "end": 891, + "loc": { + "start": { + "line": 22, + "column": 1 + }, + "end": { + "line": 24, + "column": 15 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 793, + "end": 890, + "loc": { + "start": { + "line": 22, + "column": 7 + }, + "end": { + "line": 24, + "column": 14 + } + }, + "id": { + "type": "Identifier", + "start": 793, + "end": 794, + "loc": { + "start": { + "line": 22, + "column": 7 + }, + "end": { + "line": 22, + "column": 8 + } + }, + "name": "e" + }, + "init": { + "type": "CallExpression", + "start": 797, + "end": 890, + "loc": { + "start": { + "line": 22, + "column": 11 + }, + "end": { + "line": 24, + "column": 14 + } + }, + "callee": { + "type": "MemberExpression", + "start": 797, + "end": 888, + "loc": { + "start": { + "line": 22, + "column": 11 + }, + "end": { + "line": 24, + "column": 12 + } + }, + "object": { + "type": "TSNonNullExpression", + "start": 797, + "end": 879, + "loc": { + "start": { + "line": 22, + "column": 11 + }, + "end": { + "line": 24, + "column": 3 + } + }, + "expression": { + "type": "LogicalExpression", + "start": 801, + "end": 875, + "loc": { + "start": { + "line": 23, + "column": 2 + }, + "end": { + "line": 23, + "column": 76 + } + }, + "left": { + "type": "LogicalExpression", + "start": 802, + "end": 856, + "loc": { + "start": { + "line": 23, + "column": 3 + }, + "end": { + "line": 23, + "column": 57 + } + }, + "left": { + "type": "Identifier", + "start": 802, + "end": 827, + "loc": { + "start": { + "line": 23, + "column": 3 + }, + "end": { + "line": 23, + "column": 28 + } + }, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "operator": "&&", + "right": { + "type": "Identifier", + "start": 831, + "end": 856, + "loc": { + "start": { + "line": 23, + "column": 32 + }, + "end": { + "line": 23, + "column": 57 + } + }, + "name": "bbbbbbbbbbbbbbbbbbbbbbbbb" + } + }, + "operator": "||", + "right": { + "type": "Identifier", + "start": 861, + "end": 875, + "loc": { + "start": { + "line": 23, + "column": 62 + }, + "end": { + "line": 23, + "column": 76 + } + }, + "name": "cccccccccccccc" + } + } + }, + "property": { + "type": "Identifier", + "start": 880, + "end": 888, + "loc": { + "start": { + "line": 24, + "column": 4 + }, + "end": { + "line": 24, + "column": 12 + } + }, + "name": "toString" + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " the shape does not change with what follows the base - a non-null", + "start": 660, + "end": 728 + }, + { + "type": "Line", + "value": " assertion and an optional access lay out identically", + "start": 730, + "end": 785 + } + ] + }, + { + "type": "VariableDeclaration", + "start": 894, + "end": 998, + "loc": { + "start": { + "line": 26, + "column": 1 + }, + "end": { + "line": 28, + "column": 15 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 900, + "end": 997, + "loc": { + "start": { + "line": 26, + "column": 7 + }, + "end": { + "line": 28, + "column": 14 + } + }, + "id": { + "type": "Identifier", + "start": 900, + "end": 901, + "loc": { + "start": { + "line": 26, + "column": 7 + }, + "end": { + "line": 26, + "column": 8 + } + }, + "name": "f" + }, + "init": { + "type": "ChainExpression", + "start": 904, + "end": 997, + "loc": { + "start": { + "line": 26, + "column": 11 + }, + "end": { + "line": 28, + "column": 14 + } + }, + "expression": { + "type": "CallExpression", + "start": 904, + "end": 997, + "loc": { + "start": { + "line": 26, + "column": 11 + }, + "end": { + "line": 28, + "column": 14 + } + }, + "callee": { + "type": "MemberExpression", + "start": 904, + "end": 995, + "loc": { + "start": { + "line": 26, + "column": 11 + }, + "end": { + "line": 28, + "column": 12 + } + }, + "object": { + "type": "LogicalExpression", + "start": 908, + "end": 982, + "loc": { + "start": { + "line": 27, + "column": 2 + }, + "end": { + "line": 27, + "column": 76 + } + }, + "left": { + "type": "LogicalExpression", + "start": 909, + "end": 963, + "loc": { + "start": { + "line": 27, + "column": 3 + }, + "end": { + "line": 27, + "column": 57 + } + }, + "left": { + "type": "Identifier", + "start": 909, + "end": 934, + "loc": { + "start": { + "line": 27, + "column": 3 + }, + "end": { + "line": 27, + "column": 28 + } + }, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "operator": "&&", + "right": { + "type": "Identifier", + "start": 938, + "end": 963, + "loc": { + "start": { + "line": 27, + "column": 32 + }, + "end": { + "line": 27, + "column": 57 + } + }, + "name": "bbbbbbbbbbbbbbbbbbbbbbbbb" + } + }, + "operator": "||", + "right": { + "type": "Identifier", + "start": 968, + "end": 982, + "loc": { + "start": { + "line": 27, + "column": 62 + }, + "end": { + "line": 27, + "column": 76 + } + }, + "name": "cccccccccccccc" + } + }, + "property": { + "type": "Identifier", + "start": 987, + "end": 995, + "loc": { + "start": { + "line": 28, + "column": 4 + }, + "end": { + "line": 28, + "column": 12 + } + }, + "name": "toString" + }, + "computed": false, + "optional": true + }, + "arguments": [], + "optional": false + } + } + } + ], + "kind": "const" + }, + { + "type": "VariableDeclaration", + "start": 1082, + "end": 1190, + "loc": { + "start": { + "line": 31, + "column": 1 + }, + "end": { + "line": 35, + "column": 14 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 1088, + "end": 1189, + "loc": { + "start": { + "line": 31, + "column": 7 + }, + "end": { + "line": 35, + "column": 13 + } + }, + "id": { + "type": "Identifier", + "start": 1088, + "end": 1089, + "loc": { + "start": { + "line": 31, + "column": 7 + }, + "end": { + "line": 31, + "column": 8 + } + }, + "name": "g" + }, + "init": { + "type": "CallExpression", + "start": 1092, + "end": 1189, + "loc": { + "start": { + "line": 31, + "column": 11 + }, + "end": { + "line": 35, + "column": 13 + } + }, + "callee": { + "type": "MemberExpression", + "start": 1092, + "end": 1187, + "loc": { + "start": { + "line": 31, + "column": 11 + }, + "end": { + "line": 35, + "column": 11 + } + }, + "object": { + "type": "LogicalExpression", + "start": 1096, + "end": 1175, + "loc": { + "start": { + "line": 32, + "column": 2 + }, + "end": { + "line": 34, + "column": 19 + } + }, + "left": { + "type": "LogicalExpression", + "start": 1096, + "end": 1152, + "loc": { + "start": { + "line": 32, + "column": 2 + }, + "end": { + "line": 33, + "column": 27 + } + }, + "left": { + "type": "Identifier", + "start": 1096, + "end": 1121, + "loc": { + "start": { + "line": 32, + "column": 2 + }, + "end": { + "line": 32, + "column": 27 + } + }, + "name": "aaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "operator": "&&", + "right": { + "type": "Identifier", + "start": 1127, + "end": 1152, + "loc": { + "start": { + "line": 33, + "column": 2 + }, + "end": { + "line": 33, + "column": 27 + } + }, + "name": "bbbbbbbbbbbbbbbbbbbbbbbbb" + } + }, + "operator": "&&", + "right": { + "type": "Identifier", + "start": 1158, + "end": 1175, + "loc": { + "start": { + "line": 34, + "column": 2 + }, + "end": { + "line": 34, + "column": 19 + } + }, + "name": "ccccccccccccccccc" + } + }, + "property": { + "type": "Identifier", + "start": 1179, + "end": 1187, + "loc": { + "start": { + "line": 35, + "column": 3 + }, + "end": { + "line": 35, + "column": 11 + } + }, + "name": "toString" + }, + "computed": false, + "optional": false + }, + "arguments": [], + "optional": false + } + } + ], + "kind": "const", + "leadingComments": [ + { + "type": "Line", + "value": " a flat chain has no nested operand to hold together, so every operand breaks", + "start": 1001, + "end": 1080 + } + ] + } + ], + "sourceType": "module" + }, + "attributes": [ + { + "type": "Attribute", + "start": 8, + "end": 17, + "name": "lang", + "name_loc": { + "start": { + "line": 1, + "column": 8, + "character": 8 + }, + "end": { + "line": 1, + "column": 12, + "character": 12 + } + }, + "value": [ + { + "start": 14, + "end": 16, + "type": "Text", + "raw": "ts", + "data": "ts" + } + ] + } + ] + } +} diff --git a/tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/input.svelte b/tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/input.svelte new file mode 100644 index 000000000..1557b8777 --- /dev/null +++ b/tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/input.svelte @@ -0,0 +1,36 @@ + diff --git a/tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/output_prettier.svelte b/tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/output_prettier.svelte new file mode 100644 index 000000000..5cf6f6cbe --- /dev/null +++ b/tests/fixtures/typescript/expressions/member/paren_binary_base_long_prettier_divergence/output_prettier.svelte @@ -0,0 +1,39 @@ + diff --git a/tests/fixtures/typescript/typescript_specific/non_null/long/expected.json b/tests/fixtures/typescript/typescript_specific/non_null/long/expected.json index 1e8d318da..651340e47 100644 --- a/tests/fixtures/typescript/typescript_specific/non_null/long/expected.json +++ b/tests/fixtures/typescript/typescript_specific/non_null/long/expected.json @@ -2,7 +2,7 @@ "css": null, "js": [], "start": 0, - "end": 1417, + "end": 1266, "type": "Root", "fragment": { "type": "Fragment", @@ -60,9 +60,9 @@ }, { "type": "Line", - "value": " Long expression in parens with non-null", + "value": " Long await with non-null", "start": 366, - "end": 408, + "end": 393, "loc": { "start": { "line": 15, @@ -70,15 +70,15 @@ }, "end": { "line": 15, - "column": 43 + "column": 28 } } }, { "type": "Line", - "value": " Long await with non-null", - "start": 517, - "end": 544, + "value": " Long ternary with non-null", + "start": 501, + "end": 530, "loc": { "start": { "line": 19, @@ -86,15 +86,15 @@ }, "end": { "line": 19, - "column": 28 + "column": 30 } } }, { "type": "Line", - "value": " Long ternary with non-null", - "start": 652, - "end": 681, + "value": " Long type assertion with non-null", + "start": 637, + "end": 673, "loc": { "start": { "line": 23, @@ -102,15 +102,15 @@ }, "end": { "line": 23, - "column": 30 + "column": 37 } } }, { "type": "Line", - "value": " Long type assertion with non-null", - "start": 788, - "end": 824, + "value": " Assignment with long non-null chain", + "start": 780, + "end": 818, "loc": { "start": { "line": 27, @@ -118,14 +118,14 @@ }, "end": { "line": 27, - "column": 37 + "column": 39 } } }, { "type": "Line", - "value": " Assignment with long non-null chain", - "start": 931, + "value": " Function call with long non-null args", + "start": 929, "end": 969, "loc": { "start": { @@ -134,22 +134,6 @@ }, "end": { "line": 31, - "column": 39 - } - } - }, - { - "type": "Line", - "value": " Function call with long non-null args", - "start": 1080, - "end": 1120, - "loc": { - "start": { - "line": 35, - "column": 1 - }, - "end": { - "line": 35, "column": 41 } } @@ -157,15 +141,15 @@ { "type": "Line", "value": " Nested long non-null in object", - "start": 1250, - "end": 1283, + "start": 1099, + "end": 1132, "loc": { "start": { - "line": 42, + "line": 38, "column": 1 }, "end": { - "line": 42, + "line": 38, "column": 34 } } @@ -174,19 +158,19 @@ "instance": { "type": "Script", "start": 0, - "end": 1416, + "end": 1265, "context": "default", "content": { "type": "Program", "start": 18, - "end": 1407, + "end": 1256, "loc": { "start": { "line": 1, "column": 0 }, "end": { - "line": 47, + "line": 43, "column": 9 } }, @@ -714,8 +698,8 @@ }, { "type": "ExpressionStatement", - "start": 410, - "end": 514, + "start": 395, + "end": 498, "loc": { "start": { "line": 16, @@ -723,245 +707,76 @@ }, "end": { "line": 17, - "column": 50 - } - }, - "expression": { - "type": "CallExpression", - "start": 410, - "end": 513, - "loc": { - "start": { - "line": 16, - "column": 1 - }, - "end": { - "line": 17, - "column": 49 - } - }, - "callee": { - "type": "MemberExpression", - "start": 410, - "end": 511, - "loc": { - "start": { - "line": 16, - "column": 1 - }, - "end": { - "line": 17, - "column": 47 - } - }, - "object": { - "type": "TSNonNullExpression", - "start": 410, - "end": 504, - "loc": { - "start": { - "line": 16, - "column": 1 - }, - "end": { - "line": 17, - "column": 40 - } - }, - "expression": { - "type": "LogicalExpression", - "start": 411, - "end": 502, - "loc": { - "start": { - "line": 16, - "column": 2 - }, - "end": { - "line": 17, - "column": 38 - } - }, - "left": { - "type": "MemberExpression", - "start": 411, - "end": 460, - "loc": { - "start": { - "line": 16, - "column": 2 - }, - "end": { - "line": 16, - "column": 51 - } - }, - "object": { - "type": "Identifier", - "start": 411, - "end": 434, - "loc": { - "start": { - "line": 16, - "column": 2 - }, - "end": { - "line": 16, - "column": 25 - } - }, - "name": "veryLongObjectName00000" - }, - "property": { - "type": "Identifier", - "start": 435, - "end": 460, - "loc": { - "start": { - "line": 16, - "column": 26 - }, - "end": { - "line": 16, - "column": 51 - } - }, - "name": "veryLongPropertyName00000" - }, - "computed": false, - "optional": false - }, - "operator": "||", - "right": { - "type": "Identifier", - "start": 466, - "end": 502, - "loc": { - "start": { - "line": 17, - "column": 2 - }, - "end": { - "line": 17, - "column": 38 - } - }, - "name": "alternativeVeryLongPropertyName00000" - } - } - }, - "property": { - "type": "Identifier", - "start": 505, - "end": 511, - "loc": { - "start": { - "line": 17, - "column": 41 - }, - "end": { - "line": 17, - "column": 47 - } - }, - "name": "method" - }, - "computed": false, - "optional": false - }, - "arguments": [], - "optional": false - }, - "leadingComments": [ - { - "type": "Line", - "value": " Long expression in parens with non-null", - "start": 366, - "end": 408 - } - ] - }, - { - "type": "ExpressionStatement", - "start": 546, - "end": 649, - "loc": { - "start": { - "line": 20, - "column": 1 - }, - "end": { - "line": 21, "column": 12 } }, "expression": { "type": "MemberExpression", - "start": 546, - "end": 648, + "start": 395, + "end": 497, "loc": { "start": { - "line": 20, + "line": 16, "column": 1 }, "end": { - "line": 21, + "line": 17, "column": 11 } }, "object": { "type": "TSNonNullExpression", - "start": 546, - "end": 636, + "start": 395, + "end": 485, "loc": { "start": { - "line": 20, + "line": 16, "column": 1 }, "end": { - "line": 20, + "line": 16, "column": 91 } }, "expression": { "type": "AwaitExpression", - "start": 547, - "end": 634, + "start": 396, + "end": 483, "loc": { "start": { - "line": 20, + "line": 16, "column": 2 }, "end": { - "line": 20, + "line": 16, "column": 89 } }, "argument": { "type": "CallExpression", - "start": 553, - "end": 634, + "start": 402, + "end": 483, "loc": { "start": { - "line": 20, + "line": 16, "column": 8 }, "end": { - "line": 20, + "line": 16, "column": 89 } }, "callee": { "type": "Identifier", - "start": 553, - "end": 578, + "start": 402, + "end": 427, "loc": { "start": { - "line": 20, + "line": 16, "column": 8 }, "end": { - "line": 20, + "line": 16, "column": 33 } }, @@ -970,15 +785,15 @@ "arguments": [ { "type": "Identifier", - "start": 579, - "end": 605, + "start": 428, + "end": 454, "loc": { "start": { - "line": 20, + "line": 16, "column": 34 }, "end": { - "line": 20, + "line": 16, "column": 60 } }, @@ -986,15 +801,15 @@ }, { "type": "Identifier", - "start": 607, - "end": 633, + "start": 456, + "end": 482, "loc": { "start": { - "line": 20, + "line": 16, "column": 62 }, "end": { - "line": 20, + "line": 16, "column": 88 } }, @@ -1007,15 +822,15 @@ }, "property": { "type": "Identifier", - "start": 640, - "end": 648, + "start": 489, + "end": 497, "loc": { "start": { - "line": 21, + "line": 17, "column": 3 }, "end": { - "line": 21, + "line": 17, "column": 11 } }, @@ -1028,78 +843,78 @@ { "type": "Line", "value": " Long await with non-null", - "start": 517, - "end": 544 + "start": 366, + "end": 393 } ] }, { "type": "ExpressionStatement", - "start": 683, - "end": 785, + "start": 532, + "end": 634, "loc": { "start": { - "line": 24, + "line": 20, "column": 1 }, "end": { - "line": 25, + "line": 21, "column": 12 } }, "expression": { "type": "MemberExpression", - "start": 683, - "end": 784, + "start": 532, + "end": 633, "loc": { "start": { - "line": 24, + "line": 20, "column": 1 }, "end": { - "line": 25, + "line": 21, "column": 11 } }, "object": { "type": "TSNonNullExpression", - "start": 683, - "end": 772, + "start": 532, + "end": 621, "loc": { "start": { - "line": 24, + "line": 20, "column": 1 }, "end": { - "line": 24, + "line": 20, "column": 90 } }, "expression": { "type": "ConditionalExpression", - "start": 684, - "end": 770, + "start": 533, + "end": 619, "loc": { "start": { - "line": 24, + "line": 20, "column": 2 }, "end": { - "line": 24, + "line": 20, "column": 88 } }, "test": { "type": "Identifier", - "start": 684, - "end": 717, + "start": 533, + "end": 566, "loc": { "start": { - "line": 24, + "line": 20, "column": 2 }, "end": { - "line": 24, + "line": 20, "column": 35 } }, @@ -1107,15 +922,15 @@ }, "consequent": { "type": "Identifier", - "start": 720, - "end": 743, + "start": 569, + "end": 592, "loc": { "start": { - "line": 24, + "line": 20, "column": 38 }, "end": { - "line": 24, + "line": 20, "column": 61 } }, @@ -1123,15 +938,15 @@ }, "alternate": { "type": "Identifier", - "start": 746, - "end": 770, + "start": 595, + "end": 619, "loc": { "start": { - "line": 24, + "line": 20, "column": 64 }, "end": { - "line": 24, + "line": 20, "column": 88 } }, @@ -1141,15 +956,15 @@ }, "property": { "type": "Identifier", - "start": 776, - "end": 784, + "start": 625, + "end": 633, "loc": { "start": { - "line": 25, + "line": 21, "column": 3 }, "end": { - "line": 25, + "line": 21, "column": 11 } }, @@ -1162,78 +977,78 @@ { "type": "Line", "value": " Long ternary with non-null", - "start": 652, - "end": 681 + "start": 501, + "end": 530 } ] }, { "type": "ExpressionStatement", - "start": 826, - "end": 928, + "start": 675, + "end": 777, "loc": { "start": { - "line": 28, + "line": 24, "column": 1 }, "end": { - "line": 29, + "line": 25, "column": 12 } }, "expression": { "type": "MemberExpression", - "start": 826, - "end": 927, + "start": 675, + "end": 776, "loc": { "start": { - "line": 28, + "line": 24, "column": 1 }, "end": { - "line": 29, + "line": 25, "column": 11 } }, "object": { "type": "TSNonNullExpression", - "start": 826, - "end": 915, + "start": 675, + "end": 764, "loc": { "start": { - "line": 28, + "line": 24, "column": 1 }, "end": { - "line": 28, + "line": 24, "column": 90 } }, "expression": { "type": "TSAsExpression", - "start": 827, - "end": 913, + "start": 676, + "end": 762, "loc": { "start": { - "line": 28, + "line": 24, "column": 2 }, "end": { - "line": 28, + "line": 24, "column": 88 } }, "expression": { "type": "Identifier", - "start": 827, - "end": 853, + "start": 676, + "end": 702, "loc": { "start": { - "line": 28, + "line": 24, "column": 2 }, "end": { - "line": 28, + "line": 24, "column": 28 } }, @@ -1241,44 +1056,44 @@ }, "typeAnnotation": { "type": "TSUnionType", - "start": 857, - "end": 913, + "start": 706, + "end": 762, "loc": { "start": { - "line": 28, + "line": 24, "column": 32 }, "end": { - "line": 28, + "line": 24, "column": 88 } }, "types": [ { "type": "TSTypeReference", - "start": 857, - "end": 880, + "start": 706, + "end": 729, "loc": { "start": { - "line": 28, + "line": 24, "column": 32 }, "end": { - "line": 28, + "line": 24, "column": 55 } }, "typeName": { "type": "Identifier", - "start": 857, - "end": 880, + "start": 706, + "end": 729, "loc": { "start": { - "line": 28, + "line": 24, "column": 32 }, "end": { - "line": 28, + "line": 24, "column": 55 } }, @@ -1287,29 +1102,29 @@ }, { "type": "TSTypeReference", - "start": 883, - "end": 913, + "start": 732, + "end": 762, "loc": { "start": { - "line": 28, + "line": 24, "column": 58 }, "end": { - "line": 28, + "line": 24, "column": 88 } }, "typeName": { "type": "Identifier", - "start": 883, - "end": 913, + "start": 732, + "end": 762, "loc": { "start": { - "line": 28, + "line": 24, "column": 58 }, "end": { - "line": 28, + "line": 24, "column": 88 } }, @@ -1322,15 +1137,15 @@ }, "property": { "type": "Identifier", - "start": 919, - "end": 927, + "start": 768, + "end": 776, "loc": { "start": { - "line": 29, + "line": 25, "column": 3 }, "end": { - "line": 29, + "line": 25, "column": 11 } }, @@ -1343,51 +1158,51 @@ { "type": "Line", "value": " Long type assertion with non-null", - "start": 788, - "end": 824 + "start": 637, + "end": 673 } ] }, { "type": "VariableDeclaration", - "start": 971, - "end": 1077, + "start": 820, + "end": 926, "loc": { "start": { - "line": 32, + "line": 28, "column": 1 }, "end": { - "line": 33, + "line": 29, "column": 91 } }, "declarations": [ { "type": "VariableDeclarator", - "start": 977, - "end": 1076, + "start": 826, + "end": 925, "loc": { "start": { - "line": 32, + "line": 28, "column": 7 }, "end": { - "line": 33, + "line": 29, "column": 90 } }, "id": { "type": "Identifier", - "start": 977, - "end": 983, + "start": 826, + "end": 832, "loc": { "start": { - "line": 32, + "line": 28, "column": 7 }, "end": { - "line": 32, + "line": 28, "column": 13 } }, @@ -1395,85 +1210,85 @@ }, "init": { "type": "TSNonNullExpression", - "start": 988, - "end": 1076, + "start": 837, + "end": 925, "loc": { "start": { - "line": 33, + "line": 29, "column": 2 }, "end": { - "line": 33, + "line": 29, "column": 90 } }, "expression": { "type": "MemberExpression", - "start": 988, - "end": 1075, + "start": 837, + "end": 924, "loc": { "start": { - "line": 33, + "line": 29, "column": 2 }, "end": { - "line": 33, + "line": 29, "column": 89 } }, "object": { "type": "TSNonNullExpression", - "start": 988, - "end": 1041, + "start": 837, + "end": 890, "loc": { "start": { - "line": 33, + "line": 29, "column": 2 }, "end": { - "line": 33, + "line": 29, "column": 55 } }, "expression": { "type": "MemberExpression", - "start": 988, - "end": 1040, + "start": 837, + "end": 889, "loc": { "start": { - "line": 33, + "line": 29, "column": 2 }, "end": { - "line": 33, + "line": 29, "column": 54 } }, "object": { "type": "TSNonNullExpression", - "start": 988, - "end": 1013, + "start": 837, + "end": 862, "loc": { "start": { - "line": 33, + "line": 29, "column": 2 }, "end": { - "line": 33, + "line": 29, "column": 27 } }, "expression": { "type": "Identifier", - "start": 988, - "end": 1012, + "start": 837, + "end": 861, "loc": { "start": { - "line": 33, + "line": 29, "column": 2 }, "end": { - "line": 33, + "line": 29, "column": 26 } }, @@ -1482,15 +1297,15 @@ }, "property": { "type": "Identifier", - "start": 1014, - "end": 1040, + "start": 863, + "end": 889, "loc": { "start": { - "line": 33, + "line": 29, "column": 28 }, "end": { - "line": 33, + "line": 29, "column": 54 } }, @@ -1502,15 +1317,15 @@ }, "property": { "type": "Identifier", - "start": 1042, - "end": 1075, + "start": 891, + "end": 924, "loc": { "start": { - "line": 33, + "line": 29, "column": 56 }, "end": { - "line": 33, + "line": 29, "column": 89 } }, @@ -1527,50 +1342,50 @@ { "type": "Line", "value": " Assignment with long non-null chain", - "start": 931, - "end": 969 + "start": 780, + "end": 818 } ] }, { "type": "ExpressionStatement", - "start": 1122, - "end": 1247, + "start": 971, + "end": 1096, "loc": { "start": { - "line": 36, + "line": 32, "column": 1 }, "end": { - "line": 40, + "line": 36, "column": 3 } }, "expression": { "type": "CallExpression", - "start": 1122, - "end": 1246, + "start": 971, + "end": 1095, "loc": { "start": { - "line": 36, + "line": 32, "column": 1 }, "end": { - "line": 40, + "line": 36, "column": 2 } }, "callee": { "type": "Identifier", - "start": 1122, - "end": 1142, + "start": 971, + "end": 991, "loc": { "start": { - "line": 36, + "line": 32, "column": 1 }, "end": { - "line": 36, + "line": 32, "column": 21 } }, @@ -1579,43 +1394,43 @@ "arguments": [ { "type": "MemberExpression", - "start": 1146, - "end": 1175, + "start": 995, + "end": 1024, "loc": { "start": { - "line": 37, + "line": 33, "column": 2 }, "end": { - "line": 37, + "line": 33, "column": 31 } }, "object": { "type": "TSNonNullExpression", - "start": 1146, - "end": 1166, + "start": 995, + "end": 1015, "loc": { "start": { - "line": 37, + "line": 33, "column": 2 }, "end": { - "line": 37, + "line": 33, "column": 22 } }, "expression": { "type": "Identifier", - "start": 1146, - "end": 1165, + "start": 995, + "end": 1014, "loc": { "start": { - "line": 37, + "line": 33, "column": 2 }, "end": { - "line": 37, + "line": 33, "column": 21 } }, @@ -1624,15 +1439,15 @@ }, "property": { "type": "Identifier", - "start": 1167, - "end": 1175, + "start": 1016, + "end": 1024, "loc": { "start": { - "line": 37, + "line": 33, "column": 23 }, "end": { - "line": 37, + "line": 33, "column": 31 } }, @@ -1643,43 +1458,43 @@ }, { "type": "MemberExpression", - "start": 1179, - "end": 1208, + "start": 1028, + "end": 1057, "loc": { "start": { - "line": 38, + "line": 34, "column": 2 }, "end": { - "line": 38, + "line": 34, "column": 31 } }, "object": { "type": "TSNonNullExpression", - "start": 1179, - "end": 1199, + "start": 1028, + "end": 1048, "loc": { "start": { - "line": 38, + "line": 34, "column": 2 }, "end": { - "line": 38, + "line": 34, "column": 22 } }, "expression": { "type": "Identifier", - "start": 1179, - "end": 1198, + "start": 1028, + "end": 1047, "loc": { "start": { - "line": 38, + "line": 34, "column": 2 }, "end": { - "line": 38, + "line": 34, "column": 21 } }, @@ -1688,15 +1503,15 @@ }, "property": { "type": "Identifier", - "start": 1200, - "end": 1208, + "start": 1049, + "end": 1057, "loc": { "start": { - "line": 38, + "line": 34, "column": 23 }, "end": { - "line": 38, + "line": 34, "column": 31 } }, @@ -1707,43 +1522,43 @@ }, { "type": "MemberExpression", - "start": 1212, - "end": 1243, + "start": 1061, + "end": 1092, "loc": { "start": { - "line": 39, + "line": 35, "column": 2 }, "end": { - "line": 39, + "line": 35, "column": 33 } }, "object": { "type": "TSNonNullExpression", - "start": 1212, - "end": 1234, + "start": 1061, + "end": 1083, "loc": { "start": { - "line": 39, + "line": 35, "column": 2 }, "end": { - "line": 39, + "line": 35, "column": 24 } }, "expression": { "type": "Identifier", - "start": 1212, - "end": 1233, + "start": 1061, + "end": 1082, "loc": { "start": { - "line": 39, + "line": 35, "column": 2 }, "end": { - "line": 39, + "line": 35, "column": 23 } }, @@ -1752,15 +1567,15 @@ }, "property": { "type": "Identifier", - "start": 1235, - "end": 1243, + "start": 1084, + "end": 1092, "loc": { "start": { - "line": 39, + "line": 35, "column": 25 }, "end": { - "line": 39, + "line": 35, "column": 33 } }, @@ -1776,51 +1591,51 @@ { "type": "Line", "value": " Function call with long non-null args", - "start": 1080, - "end": 1120 + "start": 929, + "end": 969 } ] }, { "type": "VariableDeclaration", - "start": 1285, - "end": 1406, + "start": 1134, + "end": 1255, "loc": { "start": { - "line": 43, + "line": 39, "column": 1 }, "end": { - "line": 46, + "line": 42, "column": 3 } }, "declarations": [ { "type": "VariableDeclarator", - "start": 1291, - "end": 1405, + "start": 1140, + "end": 1254, "loc": { "start": { - "line": 43, + "line": 39, "column": 7 }, "end": { - "line": 46, + "line": 42, "column": 2 } }, "id": { "type": "Identifier", - "start": 1291, - "end": 1294, + "start": 1140, + "end": 1143, "loc": { "start": { - "line": 43, + "line": 39, "column": 7 }, "end": { - "line": 43, + "line": 39, "column": 10 } }, @@ -1828,30 +1643,30 @@ }, "init": { "type": "ObjectExpression", - "start": 1297, - "end": 1405, + "start": 1146, + "end": 1254, "loc": { "start": { - "line": 43, + "line": 39, "column": 13 }, "end": { - "line": 46, + "line": 42, "column": 2 } }, "properties": [ { "type": "Property", - "start": 1301, - "end": 1402, + "start": 1150, + "end": 1251, "loc": { "start": { - "line": 44, + "line": 40, "column": 2 }, "end": { - "line": 45, + "line": 41, "column": 39 } }, @@ -1860,15 +1675,15 @@ "computed": false, "key": { "type": "Identifier", - "start": 1301, - "end": 1305, + "start": 1150, + "end": 1154, "loc": { "start": { - "line": 44, + "line": 40, "column": 2 }, "end": { - "line": 44, + "line": 40, "column": 6 } }, @@ -1876,85 +1691,85 @@ }, "value": { "type": "TSNonNullExpression", - "start": 1307, - "end": 1402, + "start": 1156, + "end": 1251, "loc": { "start": { - "line": 44, + "line": 40, "column": 8 }, "end": { - "line": 45, + "line": 41, "column": 39 } }, "expression": { "type": "MemberExpression", - "start": 1307, - "end": 1401, + "start": 1156, + "end": 1250, "loc": { "start": { - "line": 44, + "line": 40, "column": 8 }, "end": { - "line": 45, + "line": 41, "column": 38 } }, "object": { "type": "TSNonNullExpression", - "start": 1307, - "end": 1362, + "start": 1156, + "end": 1211, "loc": { "start": { - "line": 44, + "line": 40, "column": 8 }, "end": { - "line": 44, + "line": 40, "column": 63 } }, "expression": { "type": "MemberExpression", - "start": 1307, - "end": 1361, + "start": 1156, + "end": 1210, "loc": { "start": { - "line": 44, + "line": 40, "column": 8 }, "end": { - "line": 44, + "line": 40, "column": 62 } }, "object": { "type": "TSNonNullExpression", - "start": 1307, - "end": 1333, + "start": 1156, + "end": 1182, "loc": { "start": { - "line": 44, + "line": 40, "column": 8 }, "end": { - "line": 44, + "line": 40, "column": 34 } }, "expression": { "type": "Identifier", - "start": 1307, - "end": 1332, + "start": 1156, + "end": 1181, "loc": { "start": { - "line": 44, + "line": 40, "column": 8 }, "end": { - "line": 44, + "line": 40, "column": 33 } }, @@ -1963,15 +1778,15 @@ }, "property": { "type": "Identifier", - "start": 1334, - "end": 1361, + "start": 1183, + "end": 1210, "loc": { "start": { - "line": 44, + "line": 40, "column": 35 }, "end": { - "line": 44, + "line": 40, "column": 62 } }, @@ -1983,15 +1798,15 @@ }, "property": { "type": "Identifier", - "start": 1367, - "end": 1401, + "start": 1216, + "end": 1250, "loc": { "start": { - "line": 45, + "line": 41, "column": 4 }, "end": { - "line": 45, + "line": 41, "column": 38 } }, @@ -2012,8 +1827,8 @@ { "type": "Line", "value": " Nested long non-null in object", - "start": 1250, - "end": 1283 + "start": 1099, + "end": 1132 } ] } diff --git a/tests/fixtures/typescript/typescript_specific/non_null/long/input.svelte b/tests/fixtures/typescript/typescript_specific/non_null/long/input.svelte index 2085560c6..79b412387 100644 --- a/tests/fixtures/typescript/typescript_specific/non_null/long/input.svelte +++ b/tests/fixtures/typescript/typescript_specific/non_null/long/input.svelte @@ -12,10 +12,6 @@ .anotherVeryLongMethodName000000()! .finalMethod(); - // Long expression in parens with non-null - (veryLongObjectName00000.veryLongPropertyName00000 || - alternativeVeryLongPropertyName00000)!.method(); - // Long await with non-null (await veryLongAsyncFunctionName(veryLongArgumentOne0000000, veryLongArgumentTwo0000000))! .property; diff --git a/tests/fixtures/typescript/typescript_specific/non_null/long/unformatted_compact.svelte b/tests/fixtures/typescript/typescript_specific/non_null/long/unformatted_compact.svelte index a5fea9f18..a90e561a6 100644 --- a/tests/fixtures/typescript/typescript_specific/non_null/long/unformatted_compact.svelte +++ b/tests/fixtures/typescript/typescript_specific/non_null/long/unformatted_compact.svelte @@ -8,9 +8,6 @@ veryLongObjectName000000!.veryLongPropertyName000000!.anotherVeryLongPropertyNam // Long call chain with non-null veryLongFunctionName000000()!.veryLongMethodName000000()!.anotherVeryLongMethodName000000()!.finalMethod(); -// Long expression in parens with non-null -(veryLongObjectName00000.veryLongPropertyName00000||alternativeVeryLongPropertyName00000)!.method(); - // Long await with non-null (await veryLongAsyncFunctionName(veryLongArgumentOne0000000,veryLongArgumentTwo0000000))!.property;