Files
Michał Pierzchała df0a0f7fd2 perf(package): strip comments from the Apple runner source the npm package ships (#2467)
* perf(package): strip comments from the Apple runner source the npm package ships

The packager copies apple/runner/** into dist/ as Swift source, removing only
its AGENT_DEVICE_RUNNER_UNIT_TESTS blocks, so doc comments and design notes were
downloaded on every install: 71.9 kB of 441.2 kB of packaged runner Swift.

Add a lexical scanner for the removal. A regex cannot do this: `//` and `/*`
open a comment only in code position, raw literals move their own delimiter and
escape with the `#` count, interpolation segments hold code and further
literals, and Swift block comments nest. A construct the scanner cannot account
for throws at packaging time instead of shipping Swift that does not compile.

* fix(package): keep Swift regex literals out of the comment scanner

`#/foo//bar/#` is a valid extended regex literal with no comment in it, but the
scanner only knew the `#"` raw-string family, so it read the literal's `//` as a
line comment and shipped `let pattern = #/foo` — Swift that does not compile.
Add `#/…/#` and `##/…/##` as a literal context: matching `#` counts, the
single- and multi-line forms, Swift's own-line rule for a multi-line closing
delimiter, and the `\/` escape that keeps one from closing early.

Bare `/…/` literals stay unresolvable, because the same `/` opens a comment,
divides, and starts a regex literal, and only the parse separates them. Where
one could begin — an expression position whose `/` is not followed by a space,
a tab or `)` — packaging throws by file and line instead of rewriting bytes it
cannot prove are code. Divisions (`width/2`, `Double(3)/Double(4)`), the
recording scripts' shebang and `(/)` keep flowing through.

* fix(package): keep the packaged runner source on the checkout's line numbers

`dist/apple/runner/**` is the Swift a user's `xcodebuild` and the runner name a
file and line in (it lands in runner.log), so those numbers are only worth
reading if they point at the same line of `apple/runner/**`. Both rewriting
passes now empty the lines they remove instead of deleting them: comment removal
(889 lines, 889 B) and the pre-existing unit-test `#if` block strip, which was
moving everything below a block by up to 883 lines (3,737 lines, 3,737 B).

`dist/apple/runner/` 555,907 B -> 488,635 B (-67,272 B, -12.1%); its Swift alone
441,196 B -> 373,924 B (-15.2%). Parity costs 4,626 B of the 71,898 B the
previous head saved.

Nothing in the repo compiles the packaged source, so a mis-lex that failed to
throw would ship Swift that does not build and no gate would see it. Add
`pnpm check:packaged-runner-swift`: it packages into a throwaway root and asserts
line-count parity plus the line of every declaration each packaged file still
carries, then runs `swiftc -parse` over all 44 files. The parse half reports
itself skipped where no Swift toolchain exists, so the gate is declared on the
macOS lane, where both halves run.
2026-09-11 12:00:02 +02:00

357 lines
11 KiB
TypeScript

import assert from 'node:assert/strict';
import { test } from 'vitest';
import { stripSwiftComments } from '../strip-swift-comments.mjs';
function strip(source: string, filePath = 'Fixture.swift'): string {
return stripSwiftComments(source, filePath).contents;
}
function swift(...lines: string[]): string {
return `${lines.join('\n')}\n`;
}
// The packaged file's line N has to be the checkout's line N: `dist/apple/runner/**` is what a
// user's `xcodebuild` and the runner name a file and line in, so a removed comment empties its
// line and never deletes it. scripts/check-packaged-runner-swift.ts asserts the same property
// over all 44 shipped files.
test('empties a comment-only line instead of deleting it, keeping every line number', () => {
const source = swift(
'// header note',
'/// doc comment',
'',
'let answer = 42 // why',
'',
' // indented note',
'let next = answer',
);
const stripped = strip(source);
assert.equal(stripped, swift('', '', '', 'let answer = 42', '', '', 'let next = answer'));
assert.equal(stripped.split('\n').length, source.split('\n').length);
// The code lines sit on the lines they sat on in the source: 4 and 7, not 2 and 4.
assert.equal(stripped.split('\n')[3], 'let answer = 42');
assert.equal(stripped.split('\n')[6], 'let next = answer');
});
test('keeps line numbering when every line is a comment', () => {
const source = swift('// one', ' // two', '/* three */', '/// four');
assert.equal(strip(source), swift('', '', '', ''));
});
test('a file with no comments is returned byte for byte', () => {
const source = swift('import XCTest', '', 'let trailing = " spaces " ', 'let last = 1');
assert.equal(strip(source), source);
assert.equal(stripSwiftComments(source).removedComments, 0);
});
test('leaves // inside string literals alone', () => {
const source = swift(
'let url = "https://example.com/path" // real comment',
'let format = "%@ // %@"',
String.raw`let escaped = "quote \" then // not a comment"`,
'let empty = "" // after an empty literal',
);
assert.equal(
strip(source),
swift(
'let url = "https://example.com/path"',
'let format = "%@ // %@"',
String.raw`let escaped = "quote \" then // not a comment"`,
'let empty = ""',
),
);
});
test('leaves /* inside string literals alone', () => {
const source = swift('let glob = "/* not a comment */"', 'let real = 1 /* is a comment */');
assert.equal(strip(source), swift('let glob = "/* not a comment */"', 'let real = 1'));
});
test('preserves raw string literals and their comment-shaped contents', () => {
const source = swift(
'let json = #"{"href":"https://example.com//x"}"# // trailing',
'let pounded = ##"a "# b // c"##',
String.raw`let literalEscape = #"a \(notInterpolated) // still text"#`,
);
assert.equal(
strip(source),
swift(
'let json = #"{"href":"https://example.com//x"}"#',
'let pounded = ##"a "# b // c"##',
String.raw`let literalEscape = #"a \(notInterpolated) // still text"#`,
),
);
});
test('preserves multi-line string literals verbatim, blank and comment-shaped lines included', () => {
const source = swift(
'let usage = """',
' // not a comment',
'',
' /* also not a comment */',
' trailing spaces kept ',
' """ // trailing comment on the closing line',
'let after = 1',
);
assert.equal(
strip(source),
swift(
'let usage = """',
' // not a comment',
'',
' /* also not a comment */',
' trailing spaces kept ',
' """',
'let after = 1',
),
);
});
test('preserves a multi-line raw literal and its line continuations', () => {
const source = swift(
'let raw = #"""',
String.raw` keep "# and // and \(this)`,
' """#',
'let plain = """',
' joined \\',
' lines',
' """',
);
assert.equal(strip(source), source);
});
// Every Swift snippet in the regex-literal tests below parses clean under `xcrun swiftc -parse`
// (Swift 6.2), and so does what the scanner leaves of it. `#/foo//bar/#` has no comment in it at
// all: before the scanner knew the delimiter, it shipped `let pattern = #/foo`.
test('preserves extended regex literals whose contents are comment-shaped', () => {
const source = swift(
'let pattern = #/foo//bar/#',
'let pounded = ##/a//b/#c/##',
'let blockish = #/x/*y/#',
String.raw`let escaped = #/a\/#b/#`,
);
assert.equal(strip(source), source);
assert.equal(stripSwiftComments(source).removedComments, 0);
});
test('strips a real comment that trails an extended regex literal', () => {
const source = swift(
'let trailing = #/a//b/# // trailing',
'let blocked = ##/c/*d*/## /* block */',
'let next = 1',
);
assert.equal(
strip(source),
swift('let trailing = #/a//b/#', 'let blocked = ##/c/*d*/##', 'let next = 1'),
);
assert.equal(stripSwiftComments(source).removedComments, 2);
});
test('preserves a multi-line extended regex literal verbatim, comment-shaped lines included', () => {
const source = swift(
'let multi = #/',
' foo//bar',
' /*e*/',
String.raw` a\/#b`,
'',
' /#',
'let after = 1 // note',
);
assert.equal(
strip(source),
swift(
'let multi = #/',
' foo//bar',
' /*e*/',
String.raw` a\/#b`,
'',
' /#',
'let after = 1',
),
);
});
test('reads an unspaced division as an operator, not as a bare regex literal', () => {
const source = swift(
'#!/usr/bin/env swift',
'let half = width/2 // note',
'let ratio = Double(3)/Double(4)',
'let spaced = width / 2 // also fine',
'let divide: (Int, Int) -> Int = (/)',
);
assert.equal(
strip(source),
swift(
'#!/usr/bin/env swift',
'let half = width/2',
'let ratio = Double(3)/Double(4)',
'let spaced = width / 2',
'let divide: (Int, Int) -> Int = (/)',
),
);
});
test('reads interpolation segments as code without losing their nested literals', () => {
const source = swift(
String.raw`let line = "prefix \(makeURL("https://example.com")) suffix" // trailing`,
String.raw`let nested = "\(count(of: (a, b))) items"`,
String.raw`let rawInterpolated = #"\#(value) // text"#`,
);
assert.equal(
strip(source),
swift(
String.raw`let line = "prefix \(makeURL("https://example.com")) suffix"`,
String.raw`let nested = "\(count(of: (a, b))) items"`,
String.raw`let rawInterpolated = #"\#(value) // text"#`,
),
);
});
test('removes nested block comments as one comment', () => {
const source = swift(
'/* outer',
' /* inner // with a line comment */',
' still outer */',
'let after = 1',
);
const result = stripSwiftComments(source);
assert.equal(result.contents, swift('', '', '', 'let after = 1'));
assert.equal(result.removedComments, 1);
});
test('keeps flanking tokens apart when a block comment is removed', () => {
assert.equal(strip('let sum = a/*gap*/+b\n'), 'let sum = a +b\n');
assert.equal(strip('call(/*label*/value)\n'), 'call( value)\n');
});
test('keeps statements on separate lines when a block comment spans lines', () => {
const source = swift('let a = 1 /* spans', 'the newline */ let b = 2');
assert.equal(strip(source), swift('let a = 1', ' let b = 2'));
});
test('preserves conditional compilation directives and strips their trailing comments', () => {
const source = swift(
'#if AGENT_DEVICE_RUNNER_UNIT_TESTS // only in unit-test builds',
' #if os(iOS)',
' let platform = "ios"',
' #else',
' // macOS has no equivalent',
' let platform = "macos"',
' #endif',
'#endif',
'#if canImport(UIKit)',
'import UIKit',
'#endif',
);
assert.equal(
strip(source),
swift(
'#if AGENT_DEVICE_RUNNER_UNIT_TESTS',
' #if os(iOS)',
' let platform = "ios"',
' #else',
'',
' let platform = "macos"',
' #endif',
'#endif',
'#if canImport(UIKit)',
'import UIKit',
'#endif',
),
);
});
test('does not mistake pound directives or a shebang for a raw literal', () => {
const source = swift(
'#!/usr/bin/env swift',
'if #available(iOS 15, *) {',
' print(#function) // note',
'}',
);
assert.equal(
strip(source),
swift('#!/usr/bin/env swift', 'if #available(iOS 15, *) {', ' print(#function)', '}'),
);
});
test('drops a trailing comment on a final line without a newline', () => {
assert.equal(strip('let a = 1 // note'), 'let a = 1');
assert.equal(strip('// whole file is a comment'), '');
assert.equal(strip('let a = 1'), 'let a = 1');
});
test('counts every removed comment', () => {
const result = stripSwiftComments(swift('// one', 'let a = 1 // two', 'let b = /* three */ 2'));
assert.equal(result.removedComments, 3);
});
test('throws on an unterminated block comment rather than shipping the rest of the file', () => {
assert.throws(
() => strip(swift('let a = 1', '/* never closed', 'let b = 2')),
/Unterminated block comment in Fixture\.swift:2/,
);
});
test('throws on an unterminated string literal rather than guessing where it ends', () => {
assert.throws(
() => strip(swift('let a = 1', 'let broken = "no closing quote', 'let b = 2 // note')),
/Unterminated string literal in Fixture\.swift:2/,
);
});
test('throws when an interpolation segment never closes', () => {
assert.throws(
() => strip(swift(String.raw`let a = "\(value`)),
/Unterminated interpolation in Fixture\.swift/,
);
});
// A bare `/…/` is the one construct a scanner cannot resolve: Swift lexes a comment, a division
// and a regex literal from the same `/`, and only the parse tells them apart. Packaging fails
// rather than rewrite bytes it cannot prove are code.
test('throws on a bare regex literal instead of reading its contents as a comment', () => {
assert.throws(
() => strip(swift('let a = 1', 'let pattern = /foo//bar/')),
/Ambiguous bare regex literal or division in Fixture\.swift:2/,
);
assert.throws(
() => strip(swift('func f() -> Regex<Substring> {', String.raw` return /x\/y/`, '}')),
/Ambiguous bare regex literal or division in Fixture\.swift:2/,
);
});
test('throws on an unterminated extended regex literal', () => {
assert.throws(
() => strip(swift('let a = 1', 'let pattern = #/no closing', 'let b = 2 // note')),
/Unterminated regex literal in Fixture\.swift:2/,
);
assert.throws(
() => strip(swift('let a = 1', 'let pattern = #/', ' never closed')),
/Unterminated regex literal in Fixture\.swift \(started at line 2\)/,
);
});
test('throws when a multi-line regex literal closes mid-line', () => {
assert.throws(
() => strip(swift('let multi = #/', ' mid/#line stays', ' /#')),
/Multi-line regex literal in Fixture\.swift:1 closes mid-line at line 2/,
);
});