Skip to content

rdf/turtle: emit a space before ';' and '.' terminators (#419)#420

Merged
melvincarvalho merged 4 commits into
gh-pagesfrom
issue-419-turtle-spacing
May 10, 2026
Merged

rdf/turtle: emit a space before ';' and '.' terminators (#419)#420
melvincarvalho merged 4 commits into
gh-pagesfrom
issue-419-turtle-spacing

Conversation

@melvincarvalho

Copy link
Copy Markdown
Contributor

Closes #419.

Before / after

Before:
```turtle
foaf:name "Alice";
foaf:age 30.
```

After:
```turtle
foaf:name "Alice" ;
foaf:age 30 .
```

Matches the de-facto convention in W3C Turtle 1.1 spec examples, Apache Jena's RIOT writer, and most hand-authored Turtle in the Solid / linked-data ecosystem. n3.js's writer hardcodes the no-space form and exposes no config knob, so this is a literal-aware post-pass on the writer's output.

Safety

The naïve \S;\n → \S ;\n regex would silently corrupt string literals — "foo;bar" would become "foo ;bar". The post-pass:

  1. Stashes every string literal ("...", '...', """...""", '''...''') and every <IRI> into placeholders using a NUL-bracketed sentinel (n3.js escapes raw NUL inside literals, so the sentinel can't collide with real content)
  2. Applies the spacing regex to the redacted output — now ; and . only appear as actual statement terminators
  3. Restores placeholders

Triple-quoted strings are stashed before single-quoted (otherwise """ would be parsed as "" + ").

Test plan

Match the de-facto Turtle style used in W3C 1.1 spec examples,
Apache Jena's RIOT writer, and most hand-authored Turtle in the
Solid / linked-data ecosystem: a space between the previous
token and the statement terminator.

Before:
    <s> foaf:name "Alice";
        foaf:age 30.

After:
    <s> foaf:name "Alice" ;
        foaf:age 30 .

n3.js's writer hardcodes the no-space form (`;\n    next`) and
exposes no config knob. Approach: a literal-aware post-pass on
the writer's output.

The naïve `\S;\n` → `\S ;\n` regex is unsafe — string literals
(especially triple-quoted) can contain `;\n` or `.\n` internally,
and inserting a space inside a literal would silently CHANGE the
literal's value. So:

  1. Stash every string literal AND every <IRI> into placeholders
     using a multi-character non-token-boundary sentinel
     bracketed by NULs (n3.js escapes raw NUL inside literals
     and never emits one in real Turtle output, so the sentinel
     can't collide with real content).
  2. Apply the spacing regex to the redacted output. Now `;`/`.`
     only appear as actual statement terminators because all
     literal/IRI internals are hidden.
  3. Restore placeholders.

Stash order matters — triple-quoted before single-quoted, else
`"""` looks like an empty `""` followed by `"` to the
single-quoted regex. Same for triple-vs-single apostrophe.

Tests:
  - "emits a space before ; and . terminators": positive
  - "does NOT add a space inside literals containing ; or .":
    safety regression — the post-pass MUST NOT corrupt literal
    values like `"foo;bar"`, `"has.dot"`, `"a;b.c"`.
  - "does NOT add a space inside an IRI containing ;": safety
    regression for IRIs like `<https://example.test/path;with;semis>`.

Test count: 770 → 777 in full suite (+7: 3 #419 tests + 4 #416/#417/#415 follow-ups since main).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR targets Turtle serialization readability by enforcing a space before ; and . statement terminators (per common conventions in Turtle 1.1 examples and popular writers), and adds regression tests to ensure the behavior is applied without corrupting literals or IRIs.

Changes:

  • Adds a unit test asserting ; and . terminators are preceded by whitespace.
  • Adds safety tests ensuring the spacing pass does not modify string literals containing ;/..
  • Adds a safety test ensuring IRIs containing ; are preserved verbatim.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/turtle.test.js Outdated
Comment on lines +188 to +194
// The literals must round-trip verbatim — no inserted space.
assert.ok(content.includes('"foo;bar"'),
`literal "foo;bar" must survive verbatim, got:\n${content}`);
assert.ok(content.includes('"has.dot"'),
`literal "has.dot" must survive verbatim, got:\n${content}`);
assert.ok(content.includes('"a;b.c"'),
`literal "a;b.c" must survive verbatim, got:\n${content}`);
The two safety regression tests for #419 hardcoded that N3
serializes string literals with double quotes (`"foo;bar"`)
and IRIs as `<...>`. That made them sensitive to N3 writer
formatting choices — single quotes, long-string
`"""..."""`, IRI escaping, etc. — even when the underlying
literal value would still be preserved correctly.

Fix: parse the emitted Turtle back to quads and assert the
literal/IRI VALUES, not their lexical form. The properties
the tests actually want to enforce are:
  - "foo;bar" round-trips as a literal whose value is foo;bar
    (no inserted space)
  - <https://example.test/path;with;semis> round-trips as a
    NamedNode with that exact IRI

Both are now assertion-by-value. Resilient against any future
N3 upgrade that changes quote style, IRI escaping, or the long-
string threshold.

Test count: same 777.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 2 changed files in this pull request and generated 1 comment.

Comment thread test/turtle.test.js Outdated
Comment on lines +167 to +171
const { content } = await fromJsonLd(doc, 'text/turtle', 'https://example.test/', true);
// Every `;` must be preceded by whitespace (space or newline).
// Same for `.` at end-of-statement.
const offendingSemi = /[^\s];/.test(content);
const offendingDot = /[^\s]\.\s*$/m.test(content) || /[^\s]\.\n/.test(content);
The "emits a space before ; and ." test asserted no offending
terminators (without preceding whitespace) but didn't assert
the terminators were actually present. If a future N3 upgrade
ever switched to the "one triple per statement, no ;
continuations" output style, the negative assertions would
pass vacuously and the test would stop exercising the spacing
behavior at all.

Fix: pin the presence of at least one ` ;` and one ` .` (with
preceding whitespace) before checking for offending forms. Now
the test fails loudly if either disappears.

Test count: same 777.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 2 changed files in this pull request and generated 1 comment.

Comment thread test/turtle.test.js Outdated
Comment on lines +175 to +184
// Pin "at least one ; and one . exists" — otherwise an absent
// terminator would let the negative assertions pass vacuously.
assert.match(content, /\s;/, `output must contain at least one ; terminator, got:\n${content}`);
assert.match(content, /\s\.(?:\s|$)/, `output must contain at least one . terminator, got:\n${content}`);
// Every `;` must be preceded by whitespace (space or newline).
// Same for `.` at end-of-statement.
const offendingSemi = /[^\s];/.test(content);
const offendingDot = /[^\s]\.\s*$/m.test(content) || /[^\s]\.\n/.test(content);
assert.ok(!offendingSemi, `every ; must be preceded by whitespace, got:\n${content}`);
assert.ok(!offendingDot, `every . at line/doc end must be preceded by whitespace, got:\n${content}`);
…space

The pass-2 assertions used `\s` / `[\s]` patterns, which would
treat `\n;`, `\t;`, etc. as acceptable too. The #419 intent is
specifically a single SPACE before the terminator (matching
W3C Turtle 1.1 examples and Apache Jena's RIOT writer):
  `value ;` / `value .`

If a future N3 upgrade ever emitted `\n;` or `\t;`, the pass-2
test would have passed despite the visually-different output.

Fix: tighten the assertions to require a literal ` ;` / ` .`
(a single space). Both presence-checks and the offending-form
negative checks use ` ` (space) explicitly instead of `\s`.

Test count: same 777.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 2 changed files in this pull request and generated no new comments.

@melvincarvalho
melvincarvalho merged commit 0020444 into gh-pages May 10, 2026
4 checks passed
@melvincarvalho
melvincarvalho deleted the issue-419-turtle-spacing branch May 10, 2026 10:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rdf/turtle: emit a space before ';' and '.' for readability

2 participants