Skip to content

fix(pkg/go/utils): match declarations by full name, not by prefix - #652

Open
SoulPancake wants to merge 4 commits into
developfrom
fix/line-number-prefix-matching
Open

fix(pkg/go/utils): match declarations by full name, not by prefix#652
SoulPancake wants to merge 4 commits into
developfrom
fix/line-number-prefix-matching

Conversation

@SoulPancake

@SoulPancake SoulPancake commented Aug 4, 2026

Copy link
Copy Markdown
Member

The line-number helpers in pkg/go/utils matched a declaration with strings.HasPrefix on the name alone, so a name that is a prefix of another resolved to the wrong line: asking for document matched type documentation, owner matched define owner_group:, and less matched condition less_than(.

Description

What problem is being solved?

GetTypeLineNumber, GetRelationLineNumber, GetConditionLineNumber and GetExtendedTypeLineNumber each tested only that a trimmed line started with "<keyword> <name>", with nothing checking what followed the name:

func GetTypeLineNumber(typeName string, lines []string) int {
	return slices.IndexFunc(lines, func(line string) bool {
		return strings.HasPrefix(strings.TrimSpace(line), "type "+typeName)
	})
}

Reproduced against develop — every one of these returns the line of the longer declaration that happens to come first:

query matched returned correct
type document type documentation 2 6
relation owner define owner_group: 4 5
condition less condition less_than( 7 8

The helpers have four non-test callers, all in pkg/go/transformer/module-to-model.go: the duplicate-type, duplicate-condition, extended-type and duplicate-relation errors. In a module set where one name is a prefix of another, those errors point at the wrong declaration.

This is user-visible in a released package. pkg/go/utils/line-numbers.go and pkg/go/transformer/module-to-model.go both ship in pkg/go/v0.3.1, which is the tag openfga/cli pins.

This is the Go half of #255, which listed all three implementations' files. #260 closed it after fixing only the JS and Java copies — pkg/go/utils/line-numbers.go was named in the issue but never in the diff, so the Go bug has been live since.

How is it being solved?

By requiring the name to end where the prefix ends. The four helpers now share one matcher, and the terminator rule is derived from the lexer grammar rather than hand-written per helper:

// IDENTIFIER admits letters, digits, `_` and MINUS; EXTENDED_IDENTIFIER adds
// SLASH and DOT (OpenFGALexer.g4).
func IsNameByte(b byte) bool

declarationIndex finds the first line starting with the prefix whose next byte is not a name byte (or which ends there). Because the prefix always spans whole words — type <name>, define <name>, condition <name> — that single check is what stops document matching documentation.

Taking the terminator set from the grammar rather than requiring a specific punctuation character is what makes type core.doc a declaration of core.doc and not of core, since . is a name byte in EXTENDED_IDENTIFIER. A rule that instead demanded : or ( after the name would get that case wrong.

Only the relation helper collapses runs of spaces, mirroring the reference implementation, where {2,} normalization is applied in getRelationLineNumber alone — so define owner: still matches owner.

What changes are made to solve it?

  • pkg/go/utils/line-numbers.go: adds IsNameByte and the shared declarationIndex; the four exported helpers become one-line calls into it. Signatures, the int return and the -1 miss sentinel are all unchanged.
  • pkg/go/validation/name_validation.go: GetConditionLineNumber had its own inline terminator check requiring ( after the name. It now calls utils.IsNameByte, so the rule lives in one place. This is a deliberate loosening — see the note below.
  • tests/data/transformer-module/07-prefix-collisions/: a module fixture where documentation/document and less/less_than collide across two files, asserting the duplicate-condition error lands on dup.fga line 10 rather than an earlier less_than declaration.
  • pkg/go/utils/line-numbers_test.go: 47 cases covering each helper, including the trailing-comment form the module fixtures depend on (type other # module: core, file: core.fga), collapsed spaces, dotted names, and the three collisions above.

The only visible behaviour change is that the four transformer errors point at the correct line in collision cases. No error identity or message text changes.

A note on what this does not tighten

The matcher accepts anything that is not a name byte as a terminator, which is looser than the punctuation each declaration form actually requires. All of these match today:

type doc#x            queried as `doc`     -> matches
type doc:x            queried as `doc`     -> matches
define owner [user]   queried as `owner`   -> matches   (no colon)
condition less {      queried as `less`    -> matches   (no parameter list)

None of these are valid DSL, and the helpers are only ever called with a name the parser has already accepted from a model that parsed — so a malformed line cannot reach them in practice. Deriving the rule from the grammar was preferred over four hand-written punctuation checks because it is the same rule in every helper and it handles extended identifiers correctly. Worth flagging explicitly rather than leaving it to be discovered.

For the same reason, the two Go copies are now closer but not identical. pkg/go/validation's type and relation helpers parse the line (strings.Fields, then an exact compare; or split on :), so they reject type doc#x, type doc:x and define owner [user] where utils accepts them. Both agree on every well-formed input, including all three collision cases and the trailing-comment form. Unifying the two copies is a follow-up; this PR makes them agree on the cases that occur, not on malformed input.

Testing

From pkg/go: go build ./... clean, and go test ./... -count=1 green — utils, transformer and validation all pass, with the new module fixture exercised through the transformer path.

The three collisions were verified as failing on develop and fixed on this branch by building the helpers standalone against both versions, rather than only through the test suite.

References

closes #255

Review Checklist

  • I have clicked on "allow edits by maintainers".
  • I have added documentation for new/changed functionality in this PR or in a PR to openfga.dev [Provide a link to any relevant PRs in the references section above]
  • The correct base branch is being used, if not main
  • I have added tests to validate that the change in functionality is working as expected

The line-number helpers in pkg/go/utils matched a declaration with
strings.HasPrefix on the name alone, so a name that is a prefix of
another resolved to the wrong line: asking for `document` matched
`type documentation`, `owner` matched `define owner_group:`, and
`less` matched `condition less_than(`.

Each helper now validates what follows the name, mirroring the JS
reference in pkg/js/validator/validate-dsl.ts:

- types and extended types accept only end-of-line or a trailing
  comment, which the module fixtures rely on
  (`type other # module: core, file: core.fga`). The `#` must be
  preceded by whitespace, so `type doc#x` is not a match for `doc`.
- relations require a `:` after the name, and runs of spaces are
  collapsed first so `define  owner:` matches as `define owner:`.
- conditions require the parameter list's `(` after the name.

Signatures, the `int` return and the `-1` miss sentinel are all
unchanged, so the only visible difference is that the transformer's
duplicate-type, duplicate-condition, extended-type and duplicate-
relation errors now point at the correct line in these collision
cases. No error identity or message text changes.

This also brings the helpers into agreement with the equivalents in
pkg/go/validation, which already matched exactly; a differential
check over 540 comparisons finds no remaining divergence. That
agreement is what lets the two copies be unified without a further
behaviour change.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 159e1900-8248-409b-bd4a-82b45ad78647

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@SoulPancake
SoulPancake marked this pull request as ready for review August 5, 2026 06:52
@SoulPancake
SoulPancake requested review from a team as code owners August 5, 2026 06:52
Copilot AI lite review requested due to automatic review settings August 5, 2026 06:52

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.

Pull request overview

This PR fixes how the Go pkg/go/utils line-number helpers locate DSL declarations by ensuring they match full identifiers (and required trailing syntax) rather than using prefix-only matching, which could previously return the wrong line when names were prefixes of other names.

Changes:

  • Introduces a shared declarationIndex helper and precise “remainder” validation to avoid prefix collisions for type, extend type, and condition declarations.
  • Tightens matching rules to mirror the JS validator behavior (e.g., types allow only EOL or trailing # comment; conditions require (; relations require : and normalize repeated spaces).
  • Adds comprehensive unit tests covering collision cases, module comments, and whitespace variations.

Reviewed changes

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

File Description
pkg/go/utils/line-numbers.go Reworks declaration matching to validate what follows the name, preventing prefix-based false matches and aligning with JS behavior.
pkg/go/utils/line-numbers_test.go Adds focused tests to ensure correct line selection across name-collision and whitespace/comment edge cases.

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

Comment thread pkg/go/utils/line-numbers.go Outdated
@SoulPancake
SoulPancake force-pushed the fix/line-number-prefix-matching branch from 9797316 to a165bec Compare August 5, 2026 12:23
Siddhant-K-code
Siddhant-K-code previously approved these changes Aug 5, 2026
@senojj

senojj commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

@SoulPancake why force the caller to validate the expected tail of the string? You could simply get the index immediately following the provided prefix -- if one exists -- and check for a word termination character.

It seems that an invariant of this function is that prefix be a full word or words, and not just a partial word. Therefore, it makes more sense to assert that any matching prefix be followed by a word terminator. There is no need for the rest argument.

The declaration helpers took a rest predicate so each caller could validate
what followed the name. prefix always spans whole words, so the invariant is
simply that the name ends there: check the next byte against the grammar's
identifier rules instead.

Drops rest, endOrComment, startsWith and intraLineWhitespace. \f needs no
special handling now, since it is a name terminator like any other
non-identifier byte. normalizeSpaces stays on the relation helper only,
matching the reference's ' {2,}' handling.

The condition lookup in pkg/go/validation shares the same helper rather than
keeping a second copy of the rule.
@SoulPancake
SoulPancake force-pushed the fix/line-number-prefix-matching branch from 7c969a3 to 5569c1c Compare August 6, 2026 06:07
The prefix-matching fix had Go-only coverage, while the JS helpers carry the
same rule. tests/data/transformer-module is read by both, so a case there
pins the behaviour for each.

dup.fga declares less_than before less, so a lookup that matches on prefix
alone resolves the duplicate to the wrong line. Verified by reintroducing the
bug in each implementation: Go and JS both report line 6 instead of 10.
@SoulPancake

SoulPancake commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

Shared fixtures causing the npm audit failure to show up in this PR, checking that.

Edit: Letting that remain as a separate fix PR to not digress from the topic here.

@SoulPancake
SoulPancake requested a review from senojj August 6, 2026 06:30

@Siddhant-K-code Siddhant-K-code left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Tests/lint seems to be failing - can i send a commit to fix it or you wanna address it, @SoulPancake ?

@SoulPancake

Copy link
Copy Markdown
Member Author

@Siddhant-K-code
#652 (comment)

@Siddhant-K-code Siddhant-K-code left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for addressing the review comments! Changes look good to me; the prefix matching fix is clear & well tested. % address the lint failing please before merge!

I'd also wait for review from @senojj

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.

4 participants