Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

PreserveComments: trailing single-line comments are re-anchored to a different node, and output is not idempotent

オープン
#225 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
48/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
静か
技術スタック
csharp, sql
領域
tooling

調査の方向性

まず、TSql170Parser、Sql170ScriptGenerator、および PreserveComments を有効にした、提供されている C# の3パス再現を実行します。複数の CASE WHEN ブランチ末尾コメントについて、GenerateScript 中のコメント処理を追跡します。コメントが各ブランチまたはステートメントの句に関連付けられたままになり、2回目のフォーマットパスで変更が発生しなければ完了です。

索引モデルが issue の本文から書いたものです。

説明

Describe the bug

With PreserveComments = true, a trailing -- comment attached to a WHEN branch of a CASE expression is emitted against a different node than the one it annotated. Formatting the generated output a second time moves the comment again — this time into the middle of the FROM clause. Output only becomes stable on the third pass, by which point the comment sits in a clause unrelated to the expression it documented.

No exception is thrown and the parser reports no errors on any pass — the only symptom is wrong output, which is what makes it easy to ship unnoticed.

Two problems, one root cause:

  1. Semantic relocation — a comment that documented WHEN a = 1 ends up annotating the whole select element, and a comment that documented WHEN a = 2 ends up inside FROM. For code where comments carry the rationale for individual CASE branches, the regenerated script is actively misleading: the text is preserved but the association is lost.
  2. Non-idempotencyFormat(Format(x)) != Format(x). This breaks the usual formatter contract and makes the generator unusable behind a format-then-verify-no-diff gate, which is how formatters are normally enforced in CI.
Input
SELECT CASE WHEN a = 1 THEN 1 -- one
            WHEN a = 2 THEN 2 -- two
            ELSE 3 END AS x
FROM t;
Actual output
--- pass 1 (changed: True) ---
SELECT CASE WHEN a = 1 THEN 1 WHEN a = 2 THEN 2 ELSE 3 END AS x -- one
 -- two
FROM   t;

--- pass 2 (changed: True) ---
SELECT CASE WHEN a = 1 THEN 1 WHEN a = 2 THEN 2 ELSE 3 END AS x -- one
FROM   -- two
       t;

--- pass 3 (changed: False) ---
SELECT CASE WHEN a = 1 THEN 1 WHEN a = 2 THEN 2 ELSE 3 END AS x -- one
FROM   -- two
       t;

Note also the stray leading space on the -- two line in pass 1.

A single WHEN branch with a trailing comment is stable — two or more branches are needed to reproduce.

Expected behaviour

Each trailing comment stays attached to the construct it followed in the source, and the second pass is a no-op. Something along these lines would be acceptable:

SELECT CASE WHEN a = 1 THEN 1 -- one
            WHEN a = 2 THEN 2 -- two
            ELSE 3 END AS x
FROM   t;

If per-branch anchoring inside a collapsed expression is not feasible, then keeping every comment within the statement clause it originated in — and guaranteeing idempotency — would still be a large improvement over the current behaviour.

Repro
using Microsoft.SqlServer.TransactSql.ScriptDom;

const string sql = """
    SELECT CASE WHEN a = 1 THEN 1 -- one
                WHEN a = 2 THEN 2 -- two
                ELSE 3 END AS x
    FROM t;
    """;

static string Format(string input)
{
    var parser = new TSql170Parser(true);
    var tree = parser.Parse(new StringReader(input), out var errors);
    if (errors.Count > 0) throw new Exception(errors[0].Message);

    var generator = new Sql170ScriptGenerator(new SqlScriptGeneratorOptions
    {
        PreserveComments = true
    });
    generator.GenerateScript(tree, out var output);
    return output;
}

var current = sql;
for (var pass = 1; pass <= 3; pass++)
{
    var next = Format(current);
    Console.WriteLine($"--- pass {pass} (changed: {next != current}) ---");
    Console.WriteLine(next);
    current = next;
}
Environment
  • Microsoft.SqlServer.TransactSql.ScriptDom 180.78.1 (assembly 18.0.0.0), net8.0
  • Reproduces identically with Sql160ScriptGenerator, Sql170ScriptGenerator, Sql180ScriptGenerator (and their matching parsers)
  • .NET 10, macOS
Impact / context

We evaluated the generator as a formatter for a T-SQL codebase in which comments routinely annotate individual CASE branches. In that setting the relocation is worse than comment loss would be: the text survives, so the output looks fine, but the comment now explains a different expression — plausible enough to pass review unnoticed. The non-idempotency is a separate blocker, since it rules out enforcing the formatter with a format-then-check-for-diff step. Happy to test a fix if that would help.

Related

Adjacent but distinct: #194 (leading newline ahead of multi-line comments) is fixed and covers block comments; #20 is the original PreserveComments request. I could not find an existing report covering trailing single-line comment re-anchoring or the resulting non-idempotency.

主要言語
GAP
スター
277
フォーク
43
平均マージ
6日 17時間
マージ済み PR(30日)
3

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

microsoft/SqlScriptDOM のほかの issue

microsoft/SqlScriptDOM の issue をすべて見る

似ている issue

DevTools の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。