Skip to content

fix(linter): report L006 column as a byte offset; tighten comment span test - #840

Merged
ajitpratap0 merged 2 commits into
mainfrom
fix/l006-byte-column
Oct 21, 2026
Merged

ajitpratap0 merged 2 commits into
mainfrom
fix/l006-byte-column

Conversation

@ajitpratap0

@ajitpratap0 ajitpratap0 commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

Summary

This PR finishes the remaining items of #830, the column follow-ups from the review of #826 (refs #797).

  • Item 1 (quoted-identifier newline error column): already fixed on main by fix(tokenizer): point unterminated-literal errors at the opener (byte columns, a tab is one column) #827 (b3c85948). readQuotedIdentifierSlow builds its newline-branch error from t.toSQLPosition(startPos), the same as the EOF branch, and StringLiteralReader follows the same byte-column rule. I checked this on origin/main with the issue's repro, x\t/* c */ é\t"ab\nc". It reports E1002 at line 1, column 14, which is the byte column of the opening quote. This PR does not change it.
  • Item 2 (L006 reported column): pkg/linter/rules/style/column_alignment.go still compares indentation widths with tab = 4. It now reports Location.Column as the 1-based byte column of the column's first non-whitespace character, where a tab is one byte.
  • Item 3 (TestTokenPositions_CommentsKeepOwnPositions): the test now asserts tokens[2].Start >= Comments[0].End (line first, then column) and checks that no token overlaps the comment's half-open span. A multi-line block comment case covers the line comparison.
  • docs/LINTING_RULES.md now explains how L006 counts tabs and reports columns.

Root cause / design

getIndentSize returns a tab-expanded width (space = 1, tab = 4). L006 compared those widths, which is correct. It then reported Column: indent + 1, so the width was being used as if it were a position. Examples of wrong columns before this fix:

Line Before After
\t\td 9 3
\td 5 2
\t d 9 6

The fix keeps two parallel values for each SELECT column:

  • indents: the tab-expanded width. It is used for the mode/alignment comparison and is unchanged, so a tab still aligns with 4 spaces and the set of violations does not change.
  • cols: leadingWhitespaceBytes(line) + 1, the 1-based byte column of the first non-whitespace character. It is used only for the reported Location.

leadingWhitespaceBytes is a new byte loop over leading ' '/'\t'. getIndentSize is unchanged; I only updated its doc comment to say the result is a width, not a position.

Other linter rules audited for the same bug. I checked every rule that sets a non-constant Location.Column:

Rule Column source Byte-based?
L008 comma placement strings.Index / strings.LastIndex + 1 yes
L001 trailing whitespace len(strings.TrimRight(...)) + 1 yes
L010 redundant whitespace byte index from range + regex byte offsets yes
L007 keyword case range byte index + 1 yes
L005 long lines MaxLength + 1 over len(line) yes
L002 mixed indentation, L003 consecutive blank lines, L004 indentation depth, the others constant Column: 1 n/a

L006 was the only rule that turned an indentation width into a reported column. The []rune loop in keyword_case.fixLine rewrites text and never produces a column.

I left one pre-existing quirk alone: the SELECT DISTINCT first-column branch in L006 can never run, because the enclosing if excludes DISTINCT. It only affects the first column, which is never reported, so I kept it out of scope.

Test Plan

  • gofmt -l pkg/linter pkg/sql/tokenizer: no output.
  • go vet ./pkg/linter/... ./pkg/sql/tokenizer/: clean.
  • golangci-lint run ./pkg/linter/... ./pkg/sql/tokenizer/...: 0 issues.
  • go test -race -timeout 180s ./pkg/linter/... ./pkg/sql/tokenizer/... ./cmd/gosqlx/... ./pkg/lsp/... ./pkg/gosqlx/...: all ok.
  • go test -race -timeout 180s ./pkg/sql/tokenizer/ ./pkg/sql/ast/ ./pkg/compatibility/ (literal/SQL golden tests): all ok.
  • go build ./...: ok. The pre-commit hook (go fmt, go vet, go test -short ./...) passed.
  • New tests:
    • TestColumnAlignmentRule_TabColumnIsByteOffset has 6 cases: a tab aligned with 4 spaces, two tabs, a single tab, mixed spaces/tab/spaces, spaces against a tab majority, and a tab+space outlier with a multi-byte name. Each case checks the exact (line, column) and that the column points at the first non-whitespace byte.
    • TestLeadingWhitespaceBytes is a table test.
    • TestTokenPositions_CommentsKeepOwnPositions has single-line and multi-line comment subtests.
  • Negative control: against the old column_alignment.go, 4 of the 6 new L006 cases fail, reporting the old tab-expanded columns (for example got [{4 9}], want [{4 3}]). The two cases with no tab in the reported line pass on both versions, as expected.
  • No benchmark was run. This touches neither the tokenizer/parser/AST hot paths nor any alloc-sensitive path. L006 now appends one extra int per SELECT column line.

Breaking Changes

None to the API. L006 now reports a different Location.Column for misaligned columns indented with tabs. Space-indented lines report the same column as before, and the set of violations is unchanged.

Release note

- Linter: L006 (column alignment) now reports the 1-based byte column of a misaligned
  column's first character (a tab is one column), consistent with token and error
  positions. Tabs still count as 4 spaces when comparing alignment, so which lines are
  flagged is unchanged (#830).

Closes #830

🤖 Generated with Claude Code

https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9

…n test

L006 (column alignment) compared indentation with a tab counted as 4
columns and then reported Location.Column as that tab-expanded width + 1,
so a column indented with two tabs was reported at column 9 instead of 3.
Indentation is still compared with tab = 4, but the reported column is now
the 1-based byte column of the first non-whitespace character, matching
the byte columns documented on models.Location (#797, #826).

Also strengthen TestTokenPositions_CommentsKeepOwnPositions to assert the
token after a comment starts at or after the comment's End (line, then
column) and that no token overlaps the comment span, with a multi-line
comment case.

Refs #830

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Claude finished @ajitpratap0's task in 19s —— View job


Review of PR #840

Verdict: looks good, with only minor nits.

  • Read the diff for column_alignment.go and the docs change
  • Checked that the cols indexing lines up with indents and lines
  • Not done: I did not run the tests or lint. I only read the code. Your PR description lists passing runs and a negative control.

Correctness

  • cols, indents and lines are appended together in both places, and reset together on each new SELECT. cols[i+1] in checkColumnAlignment matches the indents[1:] loop, so the indexing is right.
  • The comparison logic still uses tab-expanded widths, so the set of violations is unchanged. Only Location.Column changes.
  • leadingWhitespaceBytes is byte-based and handles an all-whitespace line by returning len(line). Only non-empty lines reach it, so that case is safe.
  • The doc comments now say clearly that getIndentSize returns a width and not a position.

Nits

  1. The SELECT DISTINCT branch computes col (+17), but the PR description says that branch can never run. It's dead code, and the new col line adds to it. You could leave it, since it mirrors the existing indent line, or fix or remove the dead branch in a follow-up.
  2. checkColumnAlignment now takes indents, lines, cols []int, and the unused _ int argument sits next to them. Bundling the three slices into a small struct would be tidier, but it isn't needed for this PR.
  3. The docs example says "two tabs → column 3", which matches the table in the PR description.

Performance, security, tests

  • There is one extra int append per column line. That is negligible and no benchmark is needed.
  • There are no security concerns.
  • The new tests cover the tab cases, the multi-byte name case and the helper directly. The negative-control run in the description is good evidence that the tests catch the old behavior.
  • The tightened comment-span test in the tokenizer is a reasonable change.

The user-visible column change for tab-indented lines is clearly noted in the PR description and the docs. I'd also add the release note to CHANGELOG.md under [Unreleased], per CLAUDE.md. I didn't check whether it's already there.
· branch fix/l006-byte-column

@github-actions github-actions Bot added documentation Improvements or additions to documentation linter tokenizer Tokenizer / lexer changes labels Oct 21, 2026
@vercel

vercel Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
gosqlx-website Ready Ready Preview Oct 21, 2026 3:39pm UTC

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Claude finished @ajitpratap0's task in 12s —— View job


Review of PR #840

Verdict: looks good. The only thing I'd add is a changelog entry.

I read the diff for column_alignment.go and the docs. I did not run the tests or lint, so I'm relying on the runs listed in the PR description.

Correctness

  • columnCols, columnIndents and columnLines are appended together and reset together on each new SELECT. cols[i+1] lines up with the indents[1:] loop, so the indexing is right.
  • The comparison still uses tab-expanded widths, so the set of violations is unchanged. Only Location.Column changes.
  • leadingWhitespaceBytes is byte-based. It only ever sees non-empty lines, so the all-whitespace return of len(line) is safe.
  • The doc comments now say clearly that getIndentSize returns a width and not a position.

Nits (non-blocking)

  1. The SELECT DISTINCT branch now also computes col (+17). Per your description that branch is unreachable, so this is dead code that mirrors the existing indent line. I'd leave it and clean up the whole branch in a follow-up.
  2. checkColumnAlignment now takes three parallel []int slices plus an unused _ int. A small struct would be tidier, but that isn't needed here.

Changelog

  • CHANGELOG.md has an [Unreleased] section at line 8, and it already mentions L006 near line 143. I did not see an entry for this fix in the diff. Please add the release note from the PR description there, as CLAUDE.md asks.

Performance and security

  • The change adds one int append per column line, which is negligible. I have no security concerns.

Tests

  • The tab, multi-byte-name and helper tests are good. The negative control in the description shows the tests fail against the old behavior.
  • The tokenizer comment-span test is tighter now. Comparing line first, then column, and covering a multi-line comment is the right approach.
    · branch fix/l006-byte-column

@ajitpratap0
ajitpratap0 merged commit 0be81ae into main Oct 21, 2026
28 checks passed
ajitpratap0 added a commit that referenced this pull request Oct 22, 2026
* chore(parser): remove stray parser.go-e backup with conflict markers [vercel skip]

The sed backup file was committed by accident in #791. It is not compiled
but ships in the module zip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9

* chore(release): bump version to 1.16.0 [vercel skip]

gosqlx.Version and the CLI version were still 1.14.0 (v1.15.0 shipped with
them), so gosqlx --version, the MCP server info and /health reported 1.14.0.
Also bumps the VS Code extension, Claude Code plugin and marketplace, glama,
performance baseline label, llms.txt and the site's release badge. pygosqlx
__version__ now matches pyproject (0.2.0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9

* docs(changelog): finalize 1.16.0 release notes [vercel skip]

Turns [Unreleased] into [1.16.0] - 2026-10-21, adds the missing entries for
#734, #735, #780, #786, #789, #790, #791, #826, #835, #836, #837, #840, #841,
#843 and #844, updates the MCP and composite-field entries amended by #841
and #843, adds PR refs, and adds the compare links. The VS Code extension
gets a 1.16.0 entry, and split-changelog.js generates the /changelog page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9

* docs: v1.15 to v1.16 migration guide and release doc fixes [vercel skip]

MIGRATION gains a v1.15.0 -> v1.16.0 section with a behaviour-change
checklist. ERROR_CODES documents E4001 dialect gating and the 11 dialect
names. CLI_GUIDE lists --dialect on format, parse, analyze and lint and all
11 dialects. SQL_COMPATIBILITY marks Oracle CONNECT BY/PRIOR and SQL Server
PIVOT as parsed. api/mcp.md matches the current Config and middleware API,
api/models.md drops the nonexistent Location.Offset and documents byte
columns, and HIGHLIGHTING notes ANSI control-character neutralization.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9

* chore(website): regenerate sample AST and changelog pages

The website build rewrote both files on main: sample-ast.json gains the
WithOrdinality and ColumnAliases fields from #843, and v1-9-1.md drops the
emoji the changelog splitter strips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9

* docs(release): correct allocation claim, plugin history, E4001 and LoadConfig errors

Address review notes on #845: the lead paragraph understated the perf
audit's allocation cuts, the MIGRATION table listed a v1.15.0 plugin that
never shipped, E4001 also covers LIMIT under SQL Server, and LoadConfig
rejects more trusted-proxy misconfigurations than the API doc listed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9

* docs(release): make glama.json match the MCP server

List all 11 validate_sql dialects, describe lint_sql as the 10 rules it
runs (L001-L010; the CLI and SDK have 30), sync tool descriptions with
pkg/mcp/server.go, and drop the unsourced 1.38M ops/sec claim in favour
of a link to the benchmarks page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9

* docs(release): add #846-#851 to the v1.16.0 CHANGELOG and migration guide

Record the pygosqlx 0.3.0 API and wheels, the gosqlx_version double free,
LSP serverInfo/CLI version reporting, the Python CI and release workflows and
the #848 docs. MIGRATION companion table lists pygosqlx 0.3.0, libgosqlx
exports and the LSP serverInfo version, with matching behaviour-change notes.
Regenerate the v1.16.0 blog page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9

* docs(release): address v1.16.0 release review; add #852

- Release date 2026-10-22 in CHANGELOG, MIGRATION, the extension changelog
  and the generated blog post (re-stamp at tag time if it slips).
- vscode-extension CHANGELOG: Fixed entry for the #846 version reporting.
- CHANGELOG: keep the Behavior changes list tight; name TranspileError and
  the LintResult/SecurityScanResult types in the pygosqlx 0.3.0 entry.
- MIGRATION: PyPI wheels row is conditional on the pygosqlx-v0.3.0 tag.
- Add #852 (sdist builds fail without libgosqlx; release tag lookup skips v1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9

* build(release): replace deprecated goreleaser keys (formats, version_template)

goreleaser check (v2.12.7) flagged archives.format, format_overrides.format
and snapshot.name_template as deprecated; release.yml runs goreleaser
'latest'. Snapshot release dry-run builds all six archives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9

* docs(release): add #853 to the v1.16.0 CHANGELOG

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9

* feat(python): version pygosqlx in lockstep with GoSQLX (1.16.0)

pygosqlx wheels bundle libgosqlx built from the same commit, so its version
now matches the GoSQLX release. The first PyPI release is 1.16.0 (it was
0.2.0, never published); no older versions are backfilled.

- pygosqlx-release.yml also publishes on GoSQLX 'vX.Y.Z' tags (the floating
  'v1' tag does not match); 'pygosqlx-v*' stays for Python-only .postN
  releases. Both must match pyproject.toml and be on main.
- pyproject.toml / __version__ 1.16.0; the version test now asserts
  __version__ equals the bundled library's version().
- CHANGELOG, MIGRATION and python/README describe the lockstep scheme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RkzVu3JY9Y6BRWfjesWts9

---------

Co-authored-by: Ajit Pratap Singh <ajitpratapsingh@Ajits-Mac-mini-2655.local>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — b9189485 Deployed Oct 21, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation linter tokenizer Tokenizer / lexer changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(positions): remaining column inconsistencies after #797

1 participant