Add pre-commit hook to verify static analysis passes & format is correct#11941
Add pre-commit hook to verify static analysis passes & format is correct#11941camsim99 wants to merge 20 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new githooks Dart package to automate pre-commit checks, including formatting and static analysis, for staged files. The package includes an installation script, a pre-commit command runner, and associated unit tests. Review feedback highlights a potential infinite loop on Windows when locating the .git directory, raw ANSI escape sequences being printed without verifying terminal support, the use of loose any version constraints in pubspec.yaml, and redundant dead code in the test mocks.
| Directory repoRoot = Directory.current; | ||
| while (repoRoot.path != '/' && !Directory(p.join(repoRoot.path, '.git')).existsSync()) { | ||
| repoRoot = repoRoot.parent; | ||
| } | ||
|
|
||
| if (repoRoot.path == '/') { | ||
| print('❌ Could not find .git directory.'); | ||
| exit(1); | ||
| } |
There was a problem hiding this comment.
On Windows, the root directory path is not '/' (e.g., it could be 'C:\'). Since repoRoot.parent of a drive root returns the drive root itself, checking repoRoot.path != '/' will result in an infinite loop if .git is not found. Comparing repoRoot.path to repoRoot.parent.path is a cross-platform way to detect when the file system root has been reached.
| Directory repoRoot = Directory.current; | |
| while (repoRoot.path != '/' && !Directory(p.join(repoRoot.path, '.git')).existsSync()) { | |
| repoRoot = repoRoot.parent; | |
| } | |
| if (repoRoot.path == '/') { | |
| print('❌ Could not find .git directory.'); | |
| exit(1); | |
| } | |
| Directory repoRoot = Directory.current; | |
| while (repoRoot.path != repoRoot.parent.path && | |
| !Directory(p.join(repoRoot.path, '.git')).existsSync()) { | |
| repoRoot = repoRoot.parent; | |
| } | |
| if (!Directory(p.join(repoRoot.path, '.git')).existsSync()) { | |
| print('❌ Could not find .git directory.'); | |
| exit(1); | |
| } |
| Directory repoRoot = Directory.current; | ||
| while (repoRoot.path != '/' && !Directory(p.join(repoRoot.path, '.git')).existsSync()) { | ||
| repoRoot = repoRoot.parent; | ||
| } | ||
|
|
||
| if (repoRoot.path == '/') { | ||
| print('❌ Could not find .git directory.'); | ||
| return false; | ||
| } |
There was a problem hiding this comment.
On Windows, the root directory path is not '/' (e.g., it could be 'C:\'). Since repoRoot.parent of a drive root returns the drive root itself, checking repoRoot.path != '/' will result in an infinite loop if .git is not found. Comparing repoRoot.path to repoRoot.parent.path is a cross-platform way to detect when the file system root has been reached.
| Directory repoRoot = Directory.current; | |
| while (repoRoot.path != '/' && !Directory(p.join(repoRoot.path, '.git')).existsSync()) { | |
| repoRoot = repoRoot.parent; | |
| } | |
| if (repoRoot.path == '/') { | |
| print('❌ Could not find .git directory.'); | |
| return false; | |
| } | |
| Directory repoRoot = Directory.current; | |
| while (repoRoot.path != repoRoot.parent.path && | |
| !Directory(p.join(repoRoot.path, '.git')).existsSync()) { | |
| repoRoot = repoRoot.parent; | |
| } | |
| if (!Directory(p.join(repoRoot.path, '.git')).existsSync()) { | |
| print('❌ Could not find .git directory.'); | |
| return false; | |
| } |
| if (!hasError) { | ||
| stdout.write('\x1B[2K\r'); | ||
| } |
There was a problem hiding this comment.
Writing ANSI escape sequences like \x1B[2K\r directly to stdout can produce raw, unreadable characters in environments that do not support them (such as some GUI Git clients or non-interactive CI logs). Checking stdout.supportsAnsiEscapes ensures a clean fallback (like printing a newline) when ANSI escapes are not supported.
| if (!hasError) { | |
| stdout.write('\x1B[2K\r'); | |
| } | |
| if (!hasError) { | |
| stdout.write(stdout.supportsAnsiEscapes ? '\\x1B[2K\\r' : '\\n'); | |
| } |
| if (!hasError) { | ||
| stdout.write('\x1B[2K\r✅ Formatting looks good.\n'); | ||
| } |
There was a problem hiding this comment.
Ensure ANSI escape sequences are only printed when supported by the terminal to avoid raw escape characters in non-ANSI environments.
| if (!hasError) { | |
| stdout.write('\x1B[2K\r✅ Formatting looks good.\n'); | |
| } | |
| if (!hasError) { | |
| stdout.write(stdout.supportsAnsiEscapes | |
| ? '\\x1B[2K\\r✅ Formatting looks good.\\n' | |
| : '✅ Formatting looks good.\\n'); | |
| } |
| dependencies: | ||
| args: any | ||
| path: any | ||
|
|
||
| dev_dependencies: | ||
| test: any |
There was a problem hiding this comment.
Using any version constraints for dependencies is discouraged as it can lead to unexpected breaking changes or build failures when new major versions of these packages are released. It is recommended to use caret constraints to specify compatible version ranges.
dependencies:
args: ^2.4.2
path: ^1.9.0
dev_dependencies:
test: ^1.24.0| if (executable == 'git') { | ||
| if (arguments.contains('--show-toplevel')) { | ||
| return ProcessResult(0, 0, '/fake/repo/root\n', ''); | ||
| } | ||
| return ProcessResult(0, 0, 'script/githooks/lib/githooks.dart\n', ''); | ||
| } |
There was a problem hiding this comment.
| if (executable == 'git') { | ||
| if (arguments.contains('--show-toplevel')) { | ||
| return ProcessResult(0, 0, '/fake/repo/root\n', ''); | ||
| } | ||
| return ProcessResult(0, 0, 'script/githooks/lib/githooks.dart\n', ''); | ||
| } |
There was a problem hiding this comment.
| if (executable == 'git') { | ||
| if (arguments.contains('--show-toplevel')) { | ||
| return ProcessResult(0, 0, '/fake/repo/root\n', ''); | ||
| } | ||
| return ProcessResult(0, 0, 'script/githooks/lib/githooks.dart\n', ''); | ||
| } |
There was a problem hiding this comment.
| if (executable == 'git') { | ||
| if (arguments.contains('--show-toplevel')) { | ||
| return ProcessResult(0, 0, '/fake/repo/root\n', ''); | ||
| } | ||
| return ProcessResult(0, 0, 'README.md\n', ''); | ||
| } |
Adds a pre-commit hook that you can install to verify that
dart analyze --fatal-infosand the package format are correct when you make a call togit commit. Intended to ensure agents commit code that meets are quality standards.Miscellaneous notes:
script/githooks/bin/install_hooks.dartis the script to install the hook that devs would need to run to use it.githooksdirectory like https://github.com/flutter/flutter/tree/master/engine/src/flutter/tools/githooks to make it easier for folks to add new hooksPre-Review Checklist
[shared_preferences]///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.Footnotes
Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2