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

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

このコミットは、Go言語の初期のコード整形・解析ツールである pretty に、識別子の命名規則(特にエクスポートされるべきか否かによる大文字・小文字の区別)を検出するメカニズムを追加したものです。これにより、Go言語のコーディング規約に沿ったコード品質の維持を支援します。

コミット

Go言語の識別子における大文字・小文字の区別に関する問題を検出するメカニズムを追加しました。 これは pretty ツールがファイル名を命名する際に使用されます。

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

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

元コミット内容

- added mechanism to detect capitalization issues
Use: pretty -naming files

R=r
OCL=22859
CL=22859

変更の背景

Go言語には、識別子(変数名、関数名、型名など)の最初の文字が大文字か小文字かによって、その識別子がパッケージ外にエクスポートされる(公開される)か、パッケージ内でのみ利用可能(非公開)かが決まるという、非常に特徴的なルールがあります。このルールはGo言語の設計思想の根幹をなすものであり、コードの可視性とAPI設計に直接影響を与えます。

しかし、このルールは開発者が手動で常に遵守するには手間がかかり、誤りも発生しやすいため、自動的な検証メカニズムが求められていました。このコミットが行われた2009年当時、Go言語はまだ開発の初期段階にあり、pretty ツールは現在の go fmtgo vet のような役割を果たす、コードの整形や基本的な静的解析を行うツールとして開発されていました。

この変更の背景には、Go言語の重要な命名規則を開発プロセスのできるだけ早い段階で自動的にチェックし、開発者が一貫性のある高品質なコードを書けるように支援するという目的がありました。これにより、将来的な大規模なコードベースにおいても、命名規則の不一致による混乱やバグを防ぐことが期待されました。

前提知識の解説

Go言語の識別子のエクスポートルール

Go言語では、識別子の可視性(エクスポートされるか否か)は、その識別子の最初の文字が大文字か小文字かによって決定されます。

  • 大文字で始まる識別子: パッケージ外からアクセス可能です。つまり、他のパッケージからインポートして利用できます。これは「エクスポートされた」識別子と呼ばれます。
    • 例: Func, Type, VarName
  • 小文字で始まる識別子: その識別子が宣言されたパッケージ内でのみアクセス可能です。他のパッケージからは直接アクセスできません。これは「エクスポートされていない」識別子と呼ばれます。
    • 例: func, typ, varName

このシンプルながら強力なルールは、Go言語のAPI設計において非常に重要であり、意図しない内部実装の公開を防ぎ、モジュール性を高める役割を果たします。

抽象構文木 (AST: Abstract Syntax Tree)

ASTは、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやインタプリタ、あるいは静的解析ツールは、ソースコードを直接扱うのではなく、まずソースコードを解析してASTを構築し、そのASTを操作することで様々な処理を行います。

pretty ツールもGo言語のソースコードを解析し、ASTを構築します。このコミットで変更されている AST.Object は、AST内で識別子(変数、関数、型など)を表すノードの一つです。この Object に、その識別子がエクスポートされているべきか否かを判定するロジックが追加されました。

パーサー

パーサーは、ソースコードを読み込み、その構文を解析してASTを構築するプログラムの一部です。このコミットでは、usr/gri/pretty/parser.go が変更されており、パーサーがASTを構築する過程で、新しく追加された命名規則の検証ロジックを呼び出すようになっています。これにより、ソースコードの解析と同時に命名規則のチェックが行われます。

utf8 および unicode パッケージ

Go言語はUTF-8をネイティブにサポートしており、文字列はUTF-8エンコードされたバイト列として扱われます。utf8 パッケージは、UTF-8バイト列をルーン(Unicodeコードポイント)にデコードしたり、その逆を行ったりするための機能を提供します。

unicode パッケージは、Unicode文字のプロパティ(例えば、文字が大文字か小文字か、数字か記号かなど)を判定するための機能を提供します。

このコミットでは、識別子の最初の文字が大文字か小文字かを判定するために、utf8.DecodeRuneInString を使って文字列の最初のルーンを取得し、unicode.IsUpper を使ってそのルーンが大文字であるかをチェックしています。これにより、ASCII文字だけでなく、多言語の識別子に対しても正確な大文字・小文字の判定が可能になります。

技術的詳細

このコミットは、Go言語の識別子命名規則(特にエクスポートの有無による大文字・小文字の区別)を検証するための機能を pretty ツールに導入しています。

  1. AST.Object への IsExported() メソッドの追加:

    • usr/gri/pretty/ast.goObject 型のメソッドとして IsExported() bool が追加されました。
    • このメソッドは、Object が表す識別子の種類(定数、型、変数、関数など)に基づいて、その識別子の名前(obj.ident)の最初のルーンが大文字であるかを utf8.DecodeRuneInStringunicode.IsUpper を用いて判定します。
    • これにより、ASTの各識別子ノードが自身のエクスポート状態を自己判定できるようになります。
  2. naming フラグの導入:

    • usr/gri/pretty/compilation.goFlags 構造体と、usr/gri/pretty/parser.goParser 構造体に naming bool フィールドが追加されました。
    • このフラグは、コマンドライン引数(pretty -naming)を通じて設定され、命名規則の検証機能を有効にするかどうかを制御します。
    • compilation.goCompile 関数と parser.goOpen 関数が変更され、この naming フラグがパーサーに渡されるようになりました。
  3. パーサーにおける命名規則の検証ロジックの追加:

    • usr/gri/pretty/parser.goVerifyExport1VerifyExport という2つの新しい関数が追加されました。
    • VerifyExport1(p *AST.Expr, exported bool): 単一の識別子 p に対して、exported 引数で指定された期待されるエクスポート状態(大文字であるべきか、小文字であるべきか)と、obj.IsExported() の結果を比較します。不一致がある場合、P.Error を呼び出して適切なエラーメッセージ(例: " + obj.ident + " should be uppercase または " + obj.ident + " should be lowercase)を報告します。
    • VerifyExport(p *AST.Expr, exported bool): 複数の識別子(例えば、const a, b = 1, 2 のような宣言)を処理するために、VerifyExport1 をループで呼び出します。また、P.naming フラグが false の場合は、検証を行わずに早期リターンします。
    • この VerifyExport 関数は、ParseConstSpec (定数宣言), ParseTypeSpec (型宣言), ParseVarSpec (変数宣言), ParseFunctionDecl (関数宣言) といった、識別子が宣言される主要なパーサー関数内で呼び出されるようになりました。これにより、Go言語の主要な識別子すべてに対して命名規則のチェックが適用されます。
  4. エラー処理の一時的な変更:

    • usr/gri/pretty/compilation.goErrorMsg 関数内で、エラーが10個以上発生した場合に sys.exit(1) でプログラムを終了する行が一時的にコメントアウトされています。これは // TODO enable when done with name convention というコメントから、命名規則の機能開発中にはエラーで即座に終了させず、より多くのエラーを報告できるようにするための措置と考えられます。
  5. pretty ツールの出力制御:

    • usr/gri/pretty/pretty.gomain 関数で、flags.naming が有効な場合(つまり命名規則チェックが実行されている場合)は、通常のエラーチェック後にコードの整形済み出力を行わないように変更されました。これは、命名規則チェックが主な目的である場合に、余分な出力を抑制するためと考えられます。

これらの変更により、pretty ツールはGo言語の命名規則に違反する識別子を自動的に検出し、開発者に警告する能力を獲得しました。

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

usr/gri/pretty/ast.go

// Object structにIsExported()メソッドを追加
func (obj *Object) IsExported() bool {
	switch obj.kind {
	case NONE /* FUNC for now */, CONST, TYPE, VAR, FUNC:
		ch, size := utf8.DecodeRuneInString(obj.ident,  0);
		return unicode.IsUpper(ch);
	}
	return false;
}

usr/gri/pretty/parser.go

// Parser structにnamingフラグを追加
export type Parser struct {
	// Tracing/debugging
	verbose, sixg, deps, naming bool; // namingフラグが追加
	indent uint;

	// ...
}

// Open関数でnamingフラグを受け取り設定
func (P *Parser) Open(verbose, sixg, deps, naming bool, scanner *Scanner.Scanner, tokchan <-chan *Scanner.Token) {
	P.verbose = verbose;
	P.sixg = sixg;
	P.deps = deps;
	P.naming = naming; // namingフラグを設定
	P.indent = 0;

	// ...
}

// 単一の識別子のエクスポート規則を検証する関数
func (P *Parser) VerifyExport1(p *AST.Expr, exported bool) {
	obj := p.obj;
	if exported { // エクスポートされるべき場合
		if !obj.IsExported() { // 実際にはエクスポートされていない(小文字で始まる)場合
			P.Error(obj.pos, `"` + obj.ident + `" should be uppercase`); // エラー報告
		}
	} else if P.scope_lev == 0 { // エクスポートされるべきでない場合(トップレベルスコープのみ)
		if obj.IsExported() { // 実際にはエクスポートされている(大文字で始まる)場合
			P.Error(obj.pos, `"` + obj.ident + `" should be lowercase`); // エラー報告
		}
	}
}

// 複数の識別子や、namingフラグのチェックを含む検証関数
func (P *Parser) VerifyExport(p *AST.Expr, exported bool) {
	if !P.naming { // namingフラグが無効なら何もしない
		return;
	}
	for p.tok == Scanner.COMMA { // 複数の識別子がある場合(例: var a, b int)
		P.VerifyExport1(p.x, exported);
		p = p.y;
	}
	P.VerifyExport1(p, exported); // 最後の識別子を検証
}

// 各宣言パーサー関数でのVerifyExportの呼び出し例
// ParseConstSpec (定数宣言)
func (P *Parser) ParseConstSpec(exported bool, pos int) *AST.Decl {
	// ...
	P.Declare(d.ident, AST.CONST);
	P.VerifyExport(d.ident, exported); // ここで検証
	// ...
}

// ParseTypeSpec (型宣言)
func (P *Parser) ParseTypeSpec(exported bool, pos int) *AST.Decl {
	// ...
	P.VerifyExport(d.ident, exported); // ここで検証
	// ...
}

// ParseVarSpec (変数宣言)
func (P *Parser) ParseVarSpec(exported bool, pos int) *AST.Decl {
	// ...
	P.Declare(d.ident, AST.VAR);
	P.VerifyExport(d.ident, exported); // ここで検証
	// ...
}

// ParseFunctionDecl (関数宣言)
func (P *Parser) ParseFunctionDecl(exported bool) *AST.Decl {
	// ...
	if recv == nil || exported { // レシーバがない関数、またはエクスポートされる関数
		P.VerifyExport(d.ident, exported); // ここで検証
	}
	// ...
}

usr/gri/pretty/compilation.go

// Flags structにnamingフラグを追加
export type Flags struct {
	// ...
	tokenchan bool;
	naming bool; // namingフラグが追加
}

// Compile関数でparser.Openにnamingフラグを渡す
export func Compile(src_file string, flags *Flags) (*AST.Program, int) {
	// ...
	var parser Parser.Parser;
	parser.Open(flags.verbose, flags.sixg, flags.deps, flags.naming, &scanner, tstream); // namingフラグを渡す
	// ...
}

usr/gri/pretty/pretty.go

// namingフラグのコマンドライン引数登録
func init() {
	// ...
	Flag.BoolVar(&flags.naming, "naming", false, "verify export naming scheme"); // -naming コマンドライン引数を登録
}

// main関数での出力制御
func main() {
	// ...
	if !flags.naming && !*silent && !flags.testmode { // namingフラグが有効な場合は整形済みコードを出力しない
		Printer.Print(prog);
	}
	// ...
}

コアとなるコードの解説

Object.IsExported() メソッド (ast.go)

このメソッドは、Go言語の識別子がエクスポートされているかどうかを判定する中心的なロジックをカプセル化しています。 obj.ident は識別子の文字列名です。utf8.DecodeRuneInString(obj.ident, 0) は、その文字列の先頭から最初のUnicodeルーン(文字)とそのバイトサイズを取得します。Go言語の文字列はUTF-8でエンコードされているため、この関数を使って正確に最初の文字を抽出する必要があります。 次に、unicode.IsUpper(ch) は、抽出されたルーン ch がUnicodeの大文字であるかを判定します。Go言語のエクスポートルールはUnicodeの大文字・小文字に準拠しているため、このチェックが適切です。 このメソッドは、識別子の種類が CONST, TYPE, VAR, FUNC のいずれかである場合にのみチェックを行い、それ以外の場合は false を返します。これにより、Go言語の命名規則が適用されるべき識別子のみが対象となります。

Parser.VerifyExport1() および Parser.VerifyExport() 関数 (parser.go)

VerifyExport1 は、単一の識別子に対して命名規則の違反をチェックし、違反があればエラーを報告します。 exported 引数は、その識別子が「エクスポートされるべきか」という期待される状態を示します。

  • もし exportedtrue で、かつ obj.IsExported()false(つまり小文字で始まる)であれば、「大文字であるべき」というエラーを報告します。
  • もし exportedfalse で、かつ P.scope_lev == 0(トップレベルスコープ、つまりパッケージレベルの宣言)で、かつ obj.IsExported()true(つまり大文字で始まる)であれば、「小文字であるべき」というエラーを報告します。Go言語では、トップレベルで宣言された識別子のみがエクスポートの対象となるため、scope_lev == 0 のチェックが重要です。

VerifyExport は、VerifyExport1 のラッパー関数です。 まず、P.naming フラグが false であれば、この命名規則チェック機能自体が無効になっているため、すぐに処理を終了します。 次に、for p.tok == Scanner.COMMA ループを使って、const a, b, c = 1, 2, 3 のようにカンマで区切られた複数の識別子が一度に宣言されている場合に対応します。各識別子に対して VerifyExport1 を呼び出し、個別にチェックを行います。

これらの関数は、Go言語のパーサーが定数、型、変数、関数の宣言を解析する際に呼び出されます。これにより、コードが解析されると同時に、Go言語の命名規則に違反がないかが自動的に検証されるようになります。

Flags.naming とその利用 (compilation.go, parser.go, pretty.go)

naming フラグは、この命名規則チェック機能を有効にするためのスイッチです。 pretty.goinit 関数で、コマンドライン引数 -naming がこのフラグに紐付けられます。ユーザーが pretty -naming と実行することで、この機能が有効になります。 compilation.goCompile 関数は、この flags.naming の値を parser.Open に渡します。 parser.goParser.Open 関数は、受け取った naming の値を P.naming フィールドに設定します。 そして、VerifyExport 関数内で if !P.naming { return; } というガード句があるため、このフラグが false の場合は、命名規則の検証ロジックは実行されません。 また、pretty.gomain 関数では、flags.naming が有効な場合は、整形済みコードの標準出力を行わないように制御されています。これは、命名規則チェックが主目的である場合に、余分な出力を抑制し、エラー報告に集中させるための配慮です。

これらの変更は、Go言語のコード品質を自動的に向上させるための重要な一歩であり、後の go vetgolint といったツールの基礎となる考え方を示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (go.dev)
  • Go言語のソースコード (GitHub: golang/go)
  • UnicodeおよびUTF-8に関する一般的な知識
  • コンパイラ/パーサーの基本的な概念に関する一般的な知識
  • 当時のGo言語のメーリングリストや初期の設計に関する議論(具体的なURLは特定していませんが、当時の文脈を理解するために参照しました)```markdown

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

このコミットは、Go言語の初期のコード整形・解析ツールである pretty に、識別子の命名規則(特にエクスポートされるべきか否かによる大文字・小文字の区別)を検出するメカニズムを追加したものです。これにより、Go言語のコーディング規約に沿ったコード品質の維持を支援します。

コミット

Go言語の識別子における大文字・小文字の区別に関する問題を検出するメカニズムを追加しました。 これは pretty ツールがファイル名を命名する際に使用されます。

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

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

元コミット内容

- added mechanism to detect capitalization issues
Use: pretty -naming files

R=r
OCL=22859
CL=22859

変更の背景

Go言語には、識別子(変数名、関数名、型名など)の最初の文字が大文字か小文字かによって、その識別子がパッケージ外にエクスポートされる(公開される)か、パッケージ内でのみ利用可能(非公開)かが決まるという、非常に特徴的なルールがあります。このルールはGo言語の設計思想の根幹をなすものであり、コードの可視性とAPI設計に直接影響を与えます。

しかし、このルールは開発者が手動で常に遵守するには手間がかかり、誤りも発生しやすいため、自動的な検証メカニズムが求められていました。このコミットが行われた2009年当時、Go言語はまだ開発の初期段階にあり、pretty ツールは現在の go fmtgo vet のような役割を果たす、コードの整形や基本的な静的解析を行うツールとして開発されていました。

この変更の背景には、Go言語の重要な命名規則を開発プロセスのできるだけ早い段階で自動的にチェックし、開発者が一貫性のある高品質なコードを書けるように支援するという目的がありました。これにより、将来的な大規模なコードベースにおいても、命名規則の不一致による混乱やバグを防ぐことが期待されました。

前提知識の解説

Go言語の識別子のエクスポートルール

Go言語では、識別子の可視性(エクスポートされるか否か)は、その識別子の最初の文字が大文字か小文字かによって決定されます。

  • 大文字で始まる識別子: パッケージ外からアクセス可能です。つまり、他のパッケージからインポートして利用できます。これは「エクスポートされた」識別子と呼ばれます。
    • 例: Func, Type, VarName
  • 小文字で始まる識別子: その識別子が宣言されたパッケージ内でのみアクセス可能です。他のパッケージからは直接アクセスできません。これは「エクスポートされていない」識別子と呼ばれます。
    • 例: func, typ, varName

このシンプルながら強力なルールは、Go言語のAPI設計において非常に重要であり、意図しない内部実装の公開を防ぎ、モジュール性を高める役割を果たします。

抽象構文木 (AST: Abstract Syntax Tree)

ASTは、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやインタプリタ、あるいは静的解析ツールは、ソースコードを直接扱うのではなく、まずソースコードを解析してASTを構築し、そのASTを操作することで様々な処理を行います。

pretty ツールもGo言語のソースコードを解析し、ASTを構築します。このコミットで変更されている AST.Object は、AST内で識別子(変数、関数、型など)を表すノードの一つです。この Object に、その識別子がエクスポートされているべきか否かを判定するロジックが追加されました。

パーサー

パーサーは、ソースコードを読み込み、その構文を解析してASTを構築するプログラムの一部です。このコミットでは、usr/gri/pretty/parser.go が変更されており、パーサーがASTを構築する過程で、新しく追加された命名規則の検証ロジックを呼び出すようになっています。これにより、ソースコードの解析と同時に命名規則のチェックが行われます。

utf8 および unicode パッケージ

Go言語はUTF-8をネイティブにサポートしており、文字列はUTF-8エンコードされたバイト列として扱われます。utf8 パッケージは、UTF-8バイト列をルーン(Unicodeコードポイント)にデコードしたり、その逆を行ったりするための機能を提供します。

unicode パッケージは、Unicode文字のプロパティ(例えば、文字が大文字か小文字か、数字か記号かなど)を判定するための機能を提供します。

このコミットでは、識別子の最初の文字が大文字か小文字かを判定するために、utf8.DecodeRuneInString を使って文字列の最初のルーンを取得し、unicode.IsUpper を使ってそのルーンが大文字であるかをチェックしています。これにより、ASCII文字だけでなく、多言語の識別子に対しても正確な大文字・小文字の判定が可能になります。

技術的詳細

このコミットは、Go言語の識別子命名規則(特にエクスポートの有無による大文字・小文字の区別)を検証するための機能を pretty ツールに導入しています。

  1. AST.Object への IsExported() メソッドの追加:

    • usr/gri/pretty/ast.goObject 型のメソッドとして IsExported() bool が追加されました。
    • このメソッドは、Object が表す識別子の種類(定数、型、変数、関数など)に基づいて、その識別子の名前(obj.ident)の最初のルーンが大文字であるかを utf8.DecodeRuneInStringunicode.IsUpper を用いて判定します。
    • これにより、ASTの各識別子ノードが自身のエクスポート状態を自己判定できるようになります。
  2. naming フラグの導入:

    • usr/gri/pretty/compilation.goFlags 構造体と、usr/gri/pretty/parser.goParser 構造体に naming bool フィールドが追加されました。
    • このフラグは、コマンドライン引数(pretty -naming)を通じて設定され、命名規則の検証機能を有効にするかどうかを制御します。
    • compilation.goCompile 関数と parser.goOpen 関数が変更され、この naming フラグがパーサーに渡されるようになりました。
  3. パーサーにおける命名規則の検証ロジックの追加:

    • usr/gri/pretty/parser.goVerifyExport1VerifyExport という2つの新しい関数が追加されました。
    • VerifyExport1(p *AST.Expr, exported bool): 単一の識別子 p に対して、exported 引数で指定された期待されるエクスポート状態(大文字であるべきか、小文字であるべきか)と、obj.IsExported() の結果を比較します。不一致がある場合、P.Error を呼び出して適切なエラーメッセージ(例: " + obj.ident + " should be uppercase または " + obj.ident + " should be lowercase)を報告します。
    • VerifyExport(p *AST.Expr, exported bool): 複数の識別子(例えば、const a, b = 1, 2 のような宣言)を処理するために、VerifyExport1 をループで呼び出します。また、P.naming フラグが false の場合は、検証を行わずに早期リターンします。
    • この VerifyExport 関数は、ParseConstSpec (定数宣言), ParseTypeSpec (型宣言), ParseVarSpec (変数宣言), ParseFunctionDecl (関数宣言) といった、識別子が宣言される主要なパーサー関数内で呼び出されるようになりました。これにより、Go言語の主要な識別子すべてに対して命名規則のチェックが適用されます。
  4. エラー処理の一時的な変更:

    • usr/gri/pretty/compilation.goErrorMsg 関数内で、エラーが10個以上発生した場合に sys.exit(1) でプログラムを終了する行が一時的にコメントアウトされています。これは // TODO enable when done with name convention というコメントから、命名規則の機能開発中にはエラーで即座に終了させず、より多くのエラーを報告できるようにするための措置と考えられます。
  5. pretty ツールの出力制御:

    • usr/gri/pretty/pretty.gomain 関数で、flags.naming が有効な場合(つまり命名規則チェックが実行されている場合)は、通常のエラーチェック後にコードの整形済み出力を行わないように変更されました。これは、命名規則チェックが主な目的である場合に、余分な出力を抑制するためと考えられます。

これらの変更により、pretty ツールはGo言語の命名規則に違反する識別子を自動的に検出し、開発者に警告する能力を獲得しました。

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

usr/gri/pretty/ast.go

// Object structにIsExported()メソッドを追加
func (obj *Object) IsExported() bool {
	switch obj.kind {
	case NONE /* FUNC for now */, CONST, TYPE, VAR, FUNC:
		ch, size := utf8.DecodeRuneInString(obj.ident,  0);
		return unicode.IsUpper(ch);
	}
	return false;
}

usr/gri/pretty/parser.go

// Parser structにnamingフラグを追加
export type Parser struct {
	// Tracing/debugging
	verbose, sixg, deps, naming bool; // namingフラグが追加
	indent uint;

	// ...
}

// Open関数でnamingフラグを受け取り設定
func (P *Parser) Open(verbose, sixg, deps, naming bool, scanner *Scanner.Scanner, tokchan <-chan *Scanner.Token) {
	P.verbose = verbose;
	P.sixg = sixg;
	P.deps = deps;
	P.naming = naming; // namingフラグを設定
	P.indent = 0;

	// ...
}

// 単一の識別子のエクスポート規則を検証する関数
func (P *Parser) VerifyExport1(p *AST.Expr, exported bool) {
	obj := p.obj;
	if exported { // エクスポートされるべき場合
		if !obj.IsExported() { // 実際にはエクスポートされていない(小文字で始まる)場合
			P.Error(obj.pos, `"` + obj.ident + `" should be uppercase`); // エラー報告
		}
	} else if P.scope_lev == 0 { // エクスポートされるべきでない場合(トップレベルスコープのみ)
		if obj.IsExported() { // 実際にはエクスポートされている(大文字で始まる)場合
			P.Error(obj.pos, `"` + obj.ident + `" should be lowercase`); // エラー報告
		}
	}
}

// 複数の識別子や、namingフラグのチェックを含む検証関数
func (P *Parser) VerifyExport(p *AST.Expr, exported bool) {
	if !P.naming { // namingフラグが無効なら何もしない
		return;
	}
	for p.tok == Scanner.COMMA { // 複数の識別子がある場合(例: var a, b int)
		P.VerifyExport1(p.x, exported);
		p = p.y;
	}
	P.VerifyExport1(p, exported); // 最後の識別子を検証
}

// 各宣言パーサー関数でのVerifyExportの呼び出し例
// ParseConstSpec (定数宣言)
func (P *Parser) ParseConstSpec(exported bool, pos int) *AST.Decl {
	// ...
	P.Declare(d.ident, AST.CONST);
	P.VerifyExport(d.ident, exported); // ここで検証
	// ...
}

// ParseTypeSpec (型宣言)
func (P *Parser) ParseTypeSpec(exported bool, pos int) *AST.Decl {
	// ...
	P.VerifyExport(d.ident, exported); // ここで検証
	// ...
}

// ParseVarSpec (変数宣言)
func (P *Parser) ParseVarSpec(exported bool, pos int) *AST.Decl {
	// ...
	P.Declare(d.ident, AST.VAR);
	P.VerifyExport(d.ident, exported); // ここで検証
	// ...
}

// ParseFunctionDecl (関数宣言)
func (P *Parser) ParseFunctionDecl(exported bool) *AST.Decl {
	// ...
	if recv == nil || exported { // レシーバがない関数、またはエクスポートされる関数
		P.VerifyExport(d.ident, exported); // ここで検証
	}
	// ...
}

usr/gri/pretty/compilation.go

// Flags structにnamingフラグを追加
export type Flags struct {
	// ...
	tokenchan bool;
	naming bool; // namingフラグが追加
}

// Compile関数でparser.Openにnamingフラグを渡す
export func Compile(src_file string, flags *Flags) (*AST.Program, int) {
	// ...
	var parser Parser.Parser;
	parser.Open(flags.verbose, flags.sixg, flags.deps, flags.naming, &scanner, tstream); // namingフラグを渡す
	// ...
}

usr/gri/pretty/pretty.go

// namingフラグのコマンドライン引数登録
func init() {
	// ...
	Flag.BoolVar(&flags.naming, "naming", false, "verify export naming scheme"); // -naming コマンドライン引数を登録
}

// main関数での出力制御
func main() {
	// ...
	if !flags.naming && !*silent && !flags.testmode { // namingフラグが有効な場合は整形済みコードを出力しない
		Printer.Print(prog);
	}
	// ...
}

コアとなるコードの解説

Object.IsExported() メソッド (ast.go)

このメソッドは、Go言語の識別子がエクスポートされているかどうかを判定する中心的なロジックをカプセル化しています。 obj.ident は識別子の文字列名です。utf8.DecodeRuneInString(obj.ident, 0) は、その文字列の先頭から最初のUnicodeルーン(文字)とそのバイトサイズを取得します。Go言語の文字列はUTF-8でエンコードされているため、この関数を使って正確に最初の文字を抽出する必要があります。 次に、unicode.IsUpper(ch) は、抽出されたルーン ch がUnicodeの大文字であるかを判定します。Go言語のエクスポートルールはUnicodeの大文字・小文字に準拠しているため、このチェックが適切です。 このメソッドは、識別子の種類が CONST, TYPE, VAR, FUNC のいずれかである場合にのみチェックを行い、それ以外の場合は false を返します。これにより、Go言語の命名規則が適用されるべき識別子のみが対象となります。

Parser.VerifyExport1() および Parser.VerifyExport() 関数 (parser.go)

VerifyExport1 は、単一の識別子に対して命名規則の違反をチェックし、違反があればエラーを報告します。 exported 引数は、その識別子が「エクスポートされるべきか」という期待される状態を示します。

  • もし exportedtrue で、かつ obj.IsExported()false(つまり小文字で始まる)であれば、「大文字であるべき」というエラーを報告します。
  • もし exportedfalse で、かつ P.scope_lev == 0(トップレベルスコープ、つまりパッケージレベルの宣言)で、かつ obj.IsExported()true(つまり大文字で始まる)であれば、「小文字であるべき」というエラーを報告します。Go言語では、トップレベルで宣言された識別子のみがエクスポートの対象となるため、scope_lev == 0 のチェックが重要です。

VerifyExport は、VerifyExport1 のラッパー関数です。 まず、P.naming フラグが false であれば、この命名規則チェック機能自体が無効になっているため、すぐに処理を終了します。 次に、for p.tok == Scanner.COMMA ループを使って、const a, b, c = 1, 2, 3 のようにカンマで区切られた複数の識別子が一度に宣言されている場合に対応します。各識別子に対して VerifyExport1 を呼び出し、個別にチェックを行います。

これらの関数は、Go言語のパーサーが定数、型、変数、関数の宣言を解析する際に呼び出されます。これにより、コードが解析されると同時に、Go言語の命名規則に違反がないかが自動的に検証されるようになります。

Flags.naming とその利用 (compilation.go, parser.go, pretty.go)

naming フラグは、この命名規則チェック機能を有効にするためのスイッチです。 pretty.goinit 関数で、コマンドライン引数 -naming がこのフラグに紐付けられます。ユーザーが pretty -naming と実行することで、この機能が有効になります。 compilation.goCompile 関数は、この flags.naming の値を parser.Open に渡します。 parser.goParser.Open 関数は、受け取った naming の値を P.naming フィールドに設定します。 そして、VerifyExport 関数内で if !P.naming { return; } というガード句があるため、このフラグが false の場合は、命名規則の検証ロジックは実行されません。 また、pretty.gomain 関数では、flags.naming が有効な場合は、整形済みコードの標準出力を行わないように制御されています。これは、命名規則チェックが主目的である場合に、余分な出力を抑制し、エラー報告に集中させるための配慮です。

これらの変更は、Go言語のコード品質を自動的に向上させるための重要な一歩であり、後の go vetgolint といったツールの基礎となる考え方を示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (go.dev)
  • Go言語のソースコード (GitHub: golang/go)
  • UnicodeおよびUTF-8に関する一般的な知識
  • コンパイラ/パーサーの基本的な概念に関する一般的な知識
  • 当時のGo言語のメーリングリストや初期の設計に関する議論(具体的なURLは特定していませんが、当時の文脈を理解するために参照しました)