Skip to content

refactor(spec): add lossless structural parser and tree API - #332

Open
Thien Trung Vuong (trungams) wants to merge 2 commits into
mainfrom
tvuong/structural-spec-editor-parser
Open

refactor(spec): add lossless structural parser and tree API#332
Thien Trung Vuong (trungams) wants to merge 2 commits into
mainfrom
tvuong/structural-spec-editor-parser

Conversation

@trungams

@trungams Thien Trung Vuong (trungams) commented Sep 2, 2026

Copy link
Copy Markdown
Member

Summary

RPM spec files are only loosely structured, and the existing line-oriented editor has to infer section boundaries after the fact. That becomes fragile around nested conditionals, multiline macros, and sections that begin or end inside %if blocks.

This PR adds the private foundation for a structural editor. It parses a spec into a lossless tree of sections, conditionals, text, and macro definitions, and provides transactional query and mutation primitives over that tree. Parsing and serializing a valid spec preserves its contents byte for byte.

Nothing selects this parser in production yet, and the public overlay configuration is unchanged.

Motivation

Issue #214 collects several cases where line ranges are not enough to preserve section and conditional boundaries safely. A structural representation lets later changes reason about those boundaries directly without trying to evaluate RPM conditions or expand macros.

Changes

  • Parse %if/%elif/%else/%endif, including nested and empty branches.
  • Keep directive-shaped text inside multiline %define and %global bodies opaque.
  • Preserve backslash continuations, brace-delimited macros, Lua bodies, shell ${...} expressions, comments, blank lines, and original ordering.
  • Serialize valid input byte-for-byte.
  • Reject malformed structures instead of panicking or silently truncating them.
  • Add the private structuralSpec, specTree, and section handles under their final filenames and types.
  • Add transactional parse/inspect/mutate helpers and section query primitives.

Validation

  • mage build
  • mage unit

The cumulative stack was mechanically rebased onto eb9fb3f and revalidated after the rebase.

Known limitations

The parser deliberately does not evaluate RPM macros or conditional expressions. Sections created dynamically through macro expansion are therefore not visible to it. Public editor selection and actual overlay integration arrive in later PRs in the stack.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 2, 2026 23:40
@trungams Thien Trung Vuong (trungams) changed the title tvuong/structural spec editor parser refactor(spec): add lossless structural parser and tree API Sep 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The PR adds a substantial new parsing subsystem with complex lexical/state handling, so it warrants final human review despite strong test coverage and only minor review findings.

Pull request overview

Introduces a private, lossless structural RPM spec parser that builds a block tree (sections, conditionals, text, macro definitions) and provides a transactional tree API for querying and performing safe structural edits while preserving byte-for-byte serialization.

Changes:

  • Added a two-pass structural parser and serializer to round-trip spec input without altering whitespace/comments.
  • Added internal specTree / sectionHandle APIs for section queries and transactional mutation with post-mutation validation.
  • Added unit tests covering conditional nesting/branches, macro continuation opacity (including Lua/expand bodies), and removal validation invariants.
File summaries
File Description
internal/rpm/spec/tree.go Implements the structural parse/serialize logic and macro/conditional/section scanning helpers.
internal/rpm/spec/tree_test.go Adds round-trip and malformed-input tests for the structural parser, plus macro edge cases.
internal/rpm/spec/tree_raw_braces_test.go Adds regression coverage for raw-brace shell fragments inside %{expand: ...} macro bodies.
internal/rpm/spec/structural_tree_api.go Adds internal tree query/mutation primitives (sections, append/prepend lines, remove sections).
internal/rpm/spec/structural_tree_api_internal_test.go Adds transactional semantics tests and removal-safety validation tests for the tree API.
internal/rpm/spec/structural_spec.go Introduces the private structuralSpec wrapper holding raw spec lines.
Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +67 to +79
func (t *specTree) HasSection(name string) bool {
found := false

walkBlocks(t.root, func(blk *block) bool {
if blk.Kind == sectionBlock && blk.Name == name {
found = true
}

return !found
})

return found
}
Comment thread internal/rpm/spec/tree.go
Comment on lines +231 to +232
// findSectionHeaderLines returns the 0-indexed line numbers of all section headers,
// respecting line continuations (backslash-terminated lines suppress the next line).
Copilot AI review requested due to automatic review settings September 2, 2026 23:50
@trungams
Thien Trung Vuong (trungams) force-pushed the tvuong/structural-spec-editor-parser branch from 92f2d71 to 811e2f8 Compare September 2, 2026 23:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The change introduces a substantial new parsing subsystem whose correctness depends on many subtle edge cases, warranting careful human review despite good test coverage.

Review details

Suppressed comments (2)

internal/rpm/spec/structural_tree_api.go:232

  • Same as above: this can refer to a '%elif…' header as an “%if block”. Using a neutral term like “conditional block” would make the error accurate for both %if and %elif nodes.
		if wouldEmptySectionWrapper(child, removeSet) && index+1 < len(children) {
			next := children[index+1]
			if next.Kind == conditionalBlock && !containsSectionBlocks(next) && conditionalHasTextOrMacroContent(next) {
				return fmt.Errorf("content in %%if block at %#q would be orphaned after removing the preceding section:\n%w",
					next.Header, ErrConditionalSpansSections)
			}

internal/rpm/spec/tree.go:232

  • The comment says this function respects general backslash continuations, but the implementation only skips directive-shaped lines inside multi-line '%define'/'%global' bodies (and intentionally does not treat ordinary '\' continuations as structural suppression per parseTree's doc comment). This is misleading for future maintainers.
// findSectionHeaderLines returns the 0-indexed line numbers of all section headers,
// respecting line continuations (backslash-terminated lines suppress the next line).
  • Files reviewed: 6/6 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +220 to +225
if conditionalHasTextOrMacroContent(child) && containsSectionBlocks(child) {
if preceding != nil && removeSet[preceding] {
return fmt.Errorf("%%if block at %#q contains content belonging to the preceding section:\n%w",
child.Header, ErrConditionalSpansSections)
}
}
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.

2 participants