Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15262] ファイルの概要

このコミットは、Go言語のドキュメンテーション生成ツールである go/doc パッケージに、任意の種類の「ノート」(注釈)をサポートする機能を追加するものです。これまでの BUG(userid): 形式のバグコメントと同様に、// MARKER(userid): comment という形式で、開発者がコード内に特定の注釈を埋め込めるようになります。MARKER は2文字以上の大文字のアルファベットで構成されます。

コミット

commit d0f3475fda0a2ed3583a3b993be4cd526a0712a8
Author: Cosmos Nicolaou <cnicolaou@google.com>
Date:   Thu Feb 14 20:20:32 2013 -0800

    go/doc: add support for arbitrary notes
    
    Add support for arbitrary notes of the form // MARKER(userid): comment
    in the same vein as BUG(userid): A marker must be two or more upper case [A-Z] letters.
    
    R=gri, rsc, bradfitz, jscrockett01
    CC=golang-dev
    https://golang.org/cl/7322061

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/d0f3475fda0a2ed3583a3b993be4cd526a0712a8

元コミット内容

go/doc パッケージに任意のノート形式のサポートを追加します。 BUG(userid): と同様に、// MARKER(userid): comment 形式の任意のノートをサポートします。マーカーは2文字以上の大文字の [A-Z] 文字である必要があります。

変更の背景

Go言語のドキュメンテーションツール go/doc は、コード内のコメントからドキュメントを生成する際に、特定の形式のコメントを特別に扱います。特に // BUG(userid): comment という形式のコメントは、生成されるドキュメント内で「バグ」として認識され、一覧表示される機能がありました。

このコミットの背景には、BUG コメントだけでなく、TODO(未実装のタスク)、FIXME(修正が必要な箇所)、SECURITY(セキュリティ関連の懸念)など、開発者がコードベースに埋め込みたい様々な種類の注釈(ノート)が存在するというニーズがあります。これらの注釈も BUG コメントと同様に、ドキュメントとして抽出・集約できると、コードの保守性やプロジェクト管理の効率が向上します。

この変更は、既存の BUG コメントの仕組みを汎用化し、任意のマーカー(2文字以上の大文字アルファベット)を持つノートを認識・収集できるようにすることで、より柔軟なドキュメンテーションとコード管理を可能にすることを目的としています。

前提知識の解説

  • go/doc パッケージ: Go言語の標準ライブラリの一つで、Goのソースコードからドキュメンテーションを生成するためのツールです。go doc コマンドや godoc サーバーの基盤となっています。ソースコードのAST(抽象構文木)を解析し、パッケージ、型、関数、変数などのドキュメントコメントを抽出します。
  • go/ast パッケージ: Go言語のソースコードを抽象構文木(AST)として表現するためのデータ構造と関数を提供します。go/doc はこの go/ast を利用してソースコードを解析し、コメントなどの情報を取得します。
  • コメントグループ (ast.CommentGroup): Goのソースコードにおけるコメントは、ast パッケージでは ast.CommentGroup として扱われます。これは複数の ast.Comment(個々のコメント行)をまとめたものです。
  • 正規表現 (Regular Expression): 特定の文字列パターンを記述するための言語です。このコミットでは、コメントのテキストから MARKER(userid): のような特定のパターンを識別するために正規表現が使用されています。Go言語では regexp パッケージで正規表現を扱います。
  • BUG(userid): コメント: 従来の go/doc でサポートされていた特殊なコメント形式です。userid はバグを報告したユーザーの識別子(例: BUG(rsc):)。このコメントは go/doc によって抽出され、生成されるドキュメントの「Bugs」セクションに表示されます。

技術的詳細

このコミットの主要な技術的変更点は、go/doc パッケージがコメントを解析し、特定の形式のノートを抽出するメカニズムを汎用化したことです。

  1. Package 構造体の拡張: src/pkg/go/doc/doc.go にある Package 構造体に、新しいフィールド Notes map[string][]string が追加されました。これは、抽出された任意のノートを格納するためのマップです。キーはノートのマーカー(例: "TODO", "SECBUG")、値はそのマーカーに属するコメント文字列のリストです。

    type Package struct {
        // ... 既存のフィールド ...
        Bugs       []string
        // Notes such as TODO(userid): or SECURITY(userid):
        // along the lines of BUG(userid). Any marker with 2 or more upper
        // case [A-Z] letters is recognised.
        // BUG is explicitly not included in these notes but will
        // be in a subsequent change when the Bugs field above is removed.
        Notes map[string][]string
        // ... 既存のフィールド ...
    }
    
  2. reader 構造体の拡張: src/pkg/go/doc/reader.go にある reader 構造体は、ソースファイルを読み込み、ドキュメント情報を収集する内部的なヘルパ構造体です。この構造体にも notes map[string][]string フィールドが追加され、解析中にノートを一時的に保持します。

    type reader struct {
        // ... 既存のフィールド ...
        bugs      []string
        notes     map[string][]string // 新規追加
        // ... 既存のフィールド ...
    }
    
  3. 新しい正規表現の導入: コメントからノートを識別するために、新しい正規表現 noteMarker が導入されました。

    var (
        noteMarker  = regexp.MustCompile(`^/[/*][ \t]*([A-Z][A-Z]+)\(.+\):[ \t]*(.*)`) // MARKER(uid)
        noteContent = regexp.MustCompile(`[^ \n\r\t]+`)                                // at least one non-whitespace char
    )
    
    • noteMarker: // または /* で始まり、その後に空白、2文字以上の大文字アルファベット(([A-Z][A-Z]+) がマーカーをキャプチャ)、括弧で囲まれた任意の文字((.+)userid 部分をキャプチャ)、コロン、空白、そして残りのコメント内容((.*) がコメント本文をキャプチャ)が続くパターンにマッチします。
    • noteContent: コメント本文が空でないことを確認するための正規表現です。
  4. readNote 関数の追加: readNote という新しいヘルパ関数が追加されました。この関数は ast.CommentGroup を受け取り、それが MARKER(userid): comment 形式のノートであるかを判定し、もしそうであればマーカーとコメント本文を返します。

    func readNote(c *ast.CommentGroup) (marker, annotation string) {
        text := c.List[0].Text // コメントグループの最初の行のテキストを取得
        if m := noteMarker.FindStringSubmatch(text); m != nil {
            if btxt := m[2]; noteContent.MatchString(btxt) {
                // 非空のMARKERコメントの場合
                list := append([]*ast.Comment(nil), c.List...) // コメントリストをコピー
                list[0].Text = m[2] // 最初のコメント行のテキストをコメント本文に置き換え
                return m[1], (&ast.CommentGroup{List: list}).Text() // マーカーと整形されたコメント本文を返す
            }
        }
        return "", "" // マッチしない場合は空文字列を返す
    }
    
  5. readFile 関数の変更: readFile 関数は、ソースファイル内のコメントを走査し、readNote を呼び出してノートを識別するように変更されました。

    • 識別されたノートは r.notes マップに追加されます。
    • 既存の BUG コメントについては、一時的に r.bugs フィールドにも追加されますが、これは将来的に r.bugs フィールドが削除されるまでの過渡的な措置であることがコメントで示されています。
    // collect MARKER(...): annotations
    for _, c := range src.Comments {
        if marker, text := readNote(c); marker != "" {
            // Remove r.bugs in a separate CL along with
            // any necessary changes to client code.
            if marker == "BUG" {
                r.bugs = append(r.bugs, text) // BUGコメントは既存のbugsフィールドにも追加
            } else {
                if r.notes == nil { // notesマップがnilの場合の初期化
                    r.notes = make(map[string][]string)
                }
                r.notes[marker] = append(r.notes[marker], text) // その他のノートはnotesマップに追加
            }
        }
    }
    
  6. readPackage 関数の変更: readPackage 関数は、reader 構造体の notes マップを初期化するように変更されました。

    func (r *reader) readPackage(pkg *ast.Package, mode Mode) {
        // ...
        r.notes = make(map[string][]string) // notesマップの初期化
        // ...
    }
    
  7. テストデータの更新: src/pkg/go/doc/testdata/ 以下のテストファイル (a0.go, a1.go) に TODOSECBUG といった新しい形式のノートコメントが追加され、それに対応する golden ファイル (a.0.golden, a.1.golden, a.2.golden) も更新され、新しいノートが正しく抽出・表示されることを確認しています。 また、ドキュメント生成のテンプレートファイル src/pkg/go/doc/testdata/template.txt も更新され、Notes フィールドの内容をレンダリングするロジックが追加されました。

これらの変更により、go/docBUG コメントだけでなく、任意のマーカーを持つコメントを解析し、構造化されたデータとして Package.Notes に格納できるようになりました。

コアとなるコードの変更箇所

  • src/pkg/go/doc/doc.go:
    • Package 構造体に Notes map[string][]string フィールドを追加 (L.18-23)
    • New 関数で r.notesPackage.Notes に割り当て (L.95)
  • src/pkg/go/doc/reader.go:
    • reader 構造体に notes map[string][]string フィールドを追加 (L.152)
    • bug_markersbug_content の正規表現を noteMarkernoteContent に置き換え/拡張 (L.400-402)
    • readNote 関数を新規追加 (L.404-416)
    • readFile 関数内でコメントの収集ロジックを readNote を使用するように変更し、r.notes を埋めるように修正 (L.469-481)
    • readPackage 関数で r.notes マップを初期化 (L.492)
  • src/pkg/go/doc/testdata/a.0.golden, src/pkg/go/doc/testdata/a.1.golden, src/pkg/go/doc/testdata/a.2.golden:
    • SECBUGTODO のノートセクションを追加 (L.14-20)
  • src/pkg/go/doc/testdata/a0.go:
    • //TODO(uid): todo0// SECBUG(uid): sec hole 0 コメントを追加 (L.9-12)
  • src/pkg/go/doc/testdata/a1.go:
    • //TODO(uid): todo1//TODO(): ignored コメントを追加 (L.9-12)
  • src/pkg/go/doc/testdata/template.txt:
    • Notes フィールドをレンダリングするためのテンプレートロジックを追加 (L.64-68)

コアとなるコードの解説

このコミットの核心は、go/doc がコードコメントを解析し、特定のパターンにマッチするものを「ノート」として抽出する能力を汎用化した点にあります。

  1. Package.Notes フィールド: doc.go に追加された Notes map[string][]string は、最終的に生成されるドキュメントデータ構造の一部となります。これにより、go/doc を利用するアプリケーション(例: godoc コマンド)は、パッケージ内のすべての TODOSECBUG などのノートに簡単にアクセスできるようになります。キーがノートの種類(マーカー)を表し、値がその種類のノートのリストであるため、非常に構造化された形で情報が提供されます。

  2. reader.notes フィールドと readNote 関数: reader.go に追加された reader.notes は、ファイル解析中に一時的にノートを収集するための内部マップです。 readNote 関数は、この機能の中核をなすパーシングロジックです。正規表現 noteMarker を使用してコメントの最初の行を解析し、MARKER(userid): comment のパターンにマッチするかどうかを判断します。マッチした場合、マーカー(例: "TODO")と、MARKER(userid): の部分を除いた実際のコメント本文を抽出して返します。この関数は、コメントグループ全体をコピーし、最初の行のテキストをコメント本文に置き換えることで、複数行にわたるコメントも正しく処理できるようにしています。

  3. readFile におけるノートの収集ロジック: readFile 関数は、GoのソースファイルをASTとして読み込み、その中のコメントを走査します。各コメントグループに対して readNote を呼び出し、有効なノートが返された場合、そのノートを reader.notes マップに追加します。 特筆すべきは、BUG マーカーの扱いに関する部分です。

    if marker == "BUG" {
        r.bugs = append(r.bugs, text)
    } else {
        if r.notes == nil {
            r.notes = make(map[string][]string)
        }
        r.notes[marker] = append(r.notes[marker], text)
    }
    

    これは、既存の Package.Bugs フィールドとの互換性を一時的に維持するためのものです。将来的には Package.Bugs フィールドは削除され、すべてのノート(BUG を含む)が Package.Notes に一元化されることがコメントで示唆されています。これにより、go/doc の内部構造がよりクリーンで汎用的なものになります。

  4. template.txt の更新: ドキュメント生成のテンプレートが更新されたことで、go doc コマンドや godoc サーバーが、新しい Notes フィールドの内容をユーザーに表示できるようになります。これにより、開発者がコードに埋め込んだ TODOSECBUG などの注釈が、生成されるドキュメントに自動的に反映され、プロジェクトの透明性と管理が向上します。

これらの変更により、go/doc は単なるAPIドキュメント生成ツールから、コードベース全体の重要な注釈を抽出・集約する、より強力なツールへと進化しました。

関連リンク

参考にした情報源リンク

  • コミットメッセージ内の CL (Change List) リンク: https://golang.org/cl/7322061 (Goの Gerrit コードレビューシステムへのリンク)