Skip to content

Commit fc4d6b6

Browse files
authored
Merge pull request #160 from DecimalTurn/dev-spacing
2 parents 954b3ab + 823f99e commit fc4d6b6

4 files changed

Lines changed: 194 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- Patching: Preserve inline comment alignment when patching existing TOML date values with regular JavaScript `Date` objects.
13+
1014
## [1.1.0] - 2026-04-15
1115

1216
### Added

benchmark/parse-benchmark.mjs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,24 @@ function resolveWorkspacePackage(packageName) {
133133
return modulePath;
134134
}
135135

136+
function getParseOptions(implementationName) {
137+
if (implementationName === 'smol-toml') {
138+
return { maxDepth: 1010 };
139+
}
140+
141+
return undefined;
142+
}
143+
144+
function createParseRunner(TOML, implementationName) {
145+
const parseOptions = getParseOptions(implementationName);
146+
147+
if (parseOptions == null) {
148+
return (toml) => TOML.parse(toml);
149+
}
150+
151+
return (toml) => TOML.parse(toml, parseOptions);
152+
}
153+
136154
/**
137155
* Dynamically import a module, resolving ESM entry points for cached packages.
138156
* @param {string} modulePath - Absolute or relative path to the module
@@ -294,15 +312,15 @@ const allResults = [];
294312
/**
295313
* Warmup phase to allow V8 to optimize the code before benchmarking
296314
*/
297-
async function warmupModule(TOML, benchmarks, implementationName) {
315+
async function warmupModule(parseToml, benchmarks, implementationName) {
298316
console.log(c.dim(` 🔥 Warming up ${implementationName}...`));
299317
const warmupIterations = 50;
300318

301319
// Run multiple iterations to trigger V8 optimization
302320
for (let i = 0; i < warmupIterations; i++) {
303321
for (const { data } of benchmarks) {
304322
try {
305-
TOML.parse(data);
323+
parseToml(data);
306324
} catch (_) {
307325
// Ignore errors during warmup
308326
}
@@ -329,13 +347,15 @@ for (const implementation of implementationsToRun) {
329347
continue;
330348
}
331349

350+
const parseToml = createParseRunner(TOML, implementation.name);
351+
332352
// Warmup phase to ensure fair V8 optimization
333-
await warmupModule(TOML, benchmarks, implementation.name);
353+
await warmupModule(parseToml, benchmarks, implementation.name);
334354

335355
// Create benchmark suite
336356
const suite = new Suite(`${implementation.name}-parse`);
337357
benchmarks.forEach(({ name, data }) => {
338-
suite.add(name, () => TOML.parse(data));
358+
suite.add(name, () => parseToml(data));
339359
});
340360

341361
// Run benchmarks

src/__tests__/patch.test.ts

Lines changed: 165 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1856,7 +1856,7 @@ test('should patch date field from example toml', () => {
18561856
18571857
[owner]
18581858
name = "Tom Preston-Werner"
1859-
dob = 1979-05-28T07:32:00Z # First class dates? Why not?
1859+
dob = 1979-05-28T07:32:00Z # First class dates? Why not?
18601860
18611861
[database]
18621862
enabled = true
@@ -2188,6 +2188,170 @@ test('should patch offset datetime with milliseconds and preserve precision', ()
21882188
` + '\n');
21892189
});
21902190

2191+
test('should preserve aligned inline comments when patching mixed date kinds with regular Date values', () => {
2192+
const existing = dedent`
2193+
# Demo fixture covering TOML date and time value kinds
2194+
title = "Date parser demo"
2195+
2196+
[dates]
2197+
offset_date_time = 1979-05-28T07:32:00-08:00 # offset date-time
2198+
local_date_time = 1979-05-28T07:32:00 # local date-time
2199+
local_date = 1979-05-28 # local date
2200+
local_time = 07:32:00 # local time
2201+
2202+
[events]
2203+
published_at = 2026-04-17T09:15:30Z # UTC timestamp
2204+
cutoff_time = 18:45:00 # time only
2205+
release_day = 2026-05-02 # date only
2206+
` + '\n';
2207+
2208+
type Operation = { keyPath: string; changed: boolean };
2209+
2210+
const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000;
2211+
const TIME_ONLY_RE = /^\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?$/u;
2212+
const value = parse(existing);
2213+
const operations: Operation[] = [];
2214+
2215+
const incrementDateValues = (input: Record<string, unknown>, pathParts: string[]) => {
2216+
for (const [key, nestedValue] of Object.entries(input)) {
2217+
const nextPath = [...pathParts, key];
2218+
2219+
if (nestedValue instanceof Date && TIME_ONLY_RE.test(nestedValue.toISOString())) {
2220+
operations.push({ keyPath: nextPath.join('.'), changed: false });
2221+
continue;
2222+
}
2223+
2224+
if (nestedValue instanceof Date) {
2225+
input[key] = new Date(nestedValue.getTime() + ONE_DAY_IN_MS);
2226+
operations.push({ keyPath: nextPath.join('.'), changed: true });
2227+
continue;
2228+
}
2229+
2230+
if (!nestedValue || typeof nestedValue !== 'object') {
2231+
continue;
2232+
}
2233+
2234+
incrementDateValues(nestedValue as Record<string, unknown>, nextPath);
2235+
}
2236+
};
2237+
2238+
incrementDateValues(value as Record<string, unknown>, []);
2239+
2240+
expect(operations.filter(operation => operation.changed).map(operation => operation.keyPath)).toEqual([
2241+
'dates.offset_date_time',
2242+
'dates.local_date_time',
2243+
'dates.local_date',
2244+
'events.published_at',
2245+
'events.release_day'
2246+
]);
2247+
2248+
expect(operations.filter(operation => !operation.changed).map(operation => operation.keyPath)).toEqual([
2249+
'dates.local_time',
2250+
'events.cutoff_time'
2251+
]);
2252+
2253+
const patched = patch(existing, value);
2254+
2255+
expect(patched).toEqual(dedent`
2256+
# Demo fixture covering TOML date and time value kinds
2257+
title = "Date parser demo"
2258+
2259+
[dates]
2260+
offset_date_time = 1979-05-29T07:32:00-08:00 # offset date-time
2261+
local_date_time = 1979-05-29T07:32:00 # local date-time
2262+
local_date = 1979-05-29 # local date
2263+
local_time = 07:32:00 # local time
2264+
2265+
[events]
2266+
published_at = 2026-04-18T09:15:30Z # UTC timestamp
2267+
cutoff_time = 18:45:00 # time only
2268+
release_day = 2026-05-03 # date only
2269+
` + '\n');
2270+
});
2271+
2272+
test('should preserve aligned inline comments when patching single-line basic strings, arrays and numbers with same width', () => {
2273+
const existing = dedent`
2274+
# Demo fixture covering strings, arrays and number value kinds
2275+
title = "Release plan" # single-line basic string
2276+
retry_count = 3 # integer
2277+
error_rate = 0.125 # float
2278+
build_numbers = [1, 2, 3] # inline array
2279+
2280+
[service]
2281+
display_name = "API" # string in table
2282+
ports = [8080, 8081] # array in table
2283+
timeout_ms = 1500 # number in table
2284+
` + '\n';
2285+
2286+
const value = parse(existing);
2287+
2288+
value.title = 'Sprint notes';
2289+
value.retry_count = 7;
2290+
value.error_rate = 0.875;
2291+
value.build_numbers = [2, 4, 6];
2292+
value.service.display_name = 'CLI';
2293+
value.service.ports = [9000, 9001];
2294+
value.service.timeout_ms = 2500;
2295+
2296+
const patched = patch(existing, value);
2297+
2298+
expect(patched).toEqual(dedent`
2299+
# Demo fixture covering strings, arrays and number value kinds
2300+
title = "Sprint notes" # single-line basic string
2301+
retry_count = 7 # integer
2302+
error_rate = 0.875 # float
2303+
build_numbers = [2, 4, 6] # inline array
2304+
2305+
[service]
2306+
display_name = "CLI" # string in table
2307+
ports = [9000, 9001] # array in table
2308+
timeout_ms = 2500 # number in table
2309+
` + '\n');
2310+
});
2311+
2312+
// TODO: Implement comments alignment detection across lines to preserve
2313+
// even when value width changes. This is currently not supported,
2314+
// so the test is skipped.
2315+
test.skip('should preserve aligned inline comments when patching single-line basic strings, arrays and numbers with different width', () => {
2316+
const existing = dedent`
2317+
# Demo fixture covering strings, arrays and number value kinds
2318+
title = "Release plan" # single-line basic string
2319+
retry_count = 3 # integer
2320+
error_rate = 0.125 # float
2321+
build_numbers = [1, 2, 3] # inline array
2322+
2323+
[service]
2324+
display_name = "API" # string in table
2325+
ports = [8080, 8081] # array in table
2326+
timeout_ms = 1500 # number in table
2327+
` + '\n';
2328+
2329+
const value = parse(existing);
2330+
2331+
value.title = 'Release plan v2';
2332+
value.retry_count = 12;
2333+
value.error_rate = 0.5;
2334+
value.build_numbers = [1, 2, 3, 5, 8];
2335+
value.service.display_name = 'API Gateway';
2336+
value.service.ports = [8080, 8081, 8082];
2337+
value.service.timeout_ms = 25000;
2338+
2339+
const patched = patch(existing, value);
2340+
2341+
expect(patched).toEqual(dedent`
2342+
# Demo fixture covering strings, arrays and number value kinds
2343+
title = "Release plan v2" # single-line basic string
2344+
retry_count = 12 # integer
2345+
error_rate = 0.5 # float
2346+
build_numbers = [1, 2, 3, 5, 8] # inline array
2347+
2348+
[service]
2349+
display_name = "API Gateway" # string in table
2350+
ports = [8080, 8081, 8082] # array in table
2351+
timeout_ms = 25000 # number in table
2352+
` + '\n');
2353+
});
2354+
21912355
describe('should preserve all TOML date/time formats when patching', () => {
21922356
const testCases = [
21932357
{

src/patch.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,7 @@ function preserveFormatting(existing: Value, replacement: Value): void {
173173
// Update the replacement with the properly formatted date
174174
replacement.value = formattedDate;
175175
replacement.raw = formattedDate.toISOString();
176-
177-
// Adjust the location information to match the new raw length
178-
const lengthDiff = replacement.raw.length - originalRaw.length;
179-
if (lengthDiff !== 0) {
180-
replacement.loc.end.column = replacement.loc.start.column + replacement.raw.length;
181-
}
176+
replacement.loc.end.column = replacement.loc.start.column + replacement.raw.length;
182177
}
183178

184179
// Preserve array trailing comma format

0 commit comments

Comments
 (0)