Filip Hráček / text /
There’s this simple lint that’s used internally at Google that will seem inelegant to purists but is actually really useful in practice. And I often wish it was open source so I could use it in my projects now that I've been outside Google for 4 years.
I’m talking about LINT.IfChange. It works like this:
// LINT.IfChange// LINT.ThenChange(path/to/other/file.dart)That’s it.
Now, in the ideal world, we wouldn’t need this lint at all, right?
So, in the real world, LINT.IfChange is really helpful. You can search GitHub for “ThenChange” and you’ll find many examples (all of them in open source Google libraries, as far as I can tell).
TensorFlow uses it to make sure dependency versions in the Bazel build file are the same as the ones in the Python file.
# Fetch MLMD repo from GitHub.
tfx_github_archive(
name = “com_github_google_ml_metadata”,
repo = “google/ml-metadata”,
# LINT.IfChange
tag = “v1.16.0”,
# LINT.ThenChange(//tfx/dependencies.py)
)
Chromium uses it to make sure their constants in C++ are the same as the ones in Java.
// LINT.IfChange
#define LOW_MEMORY_DEVICE_THRESHOLD_MB 1024
// LINT.ThenChange(//base/android/java/src/org/chromium/base/SysUtils.java)
The Language Interpretability Tool uses it to make sure their Python backend code stays in sync with their TypeScript front-end code.
# LINT.IfChange
class ComponentInfo(TypedDict):
configSpec: types.Spec # Named for JSON struct
metaSpec: types.Spec # Named for JSON struct
description: str
# LINT.ThenChange(./client/lib/types.ts)
You get the picture.
I love LINT.IfChange so much that I would even argue that it should be sometimes used instead of static typing.
Here’s an example.
Let’s say you have a special mode in your app that lets you bring up debugging information. For example, in GIANT ROBOT GAME, when I press Ctrl-Shift-D, I get a debugging overlay, and when I press Ctrl-D over an entity, I get lots of information about that entity. YouTube has “Stats for Nerds” (try right clicking a video on desktop). A social media app might have something similar for posts.
Now, it’s easy to forget to add things to these debugging overlays. I, for one, forget all the time. Entities in my game get new capabilities and new data — and then I don’t add them to the debugging modal. Only when I later need to debug something and I bring up the modal, I realize my error. (Too late if it’s in production.)
This could be addressed in a type-safe way. Every type of entity must publish some kind of list of DiagnosticableAspect objects that the debugging modal simply displays.
import 'package:my_app/src/debugging/diagnosticable.dart';
class TroutMissile implements Diagnosticable {
Vector2 velocity;
Vector2 acceleration;
@override
List<DiagnosticableAspect> get debuggingAspects => [...];
// ...
}
But this has problems.
DiagnosticableAspect needs to be able to have all that capability, duplicating functionality of your UI framework in the process. Also, your simple object now needs to provide all that info about images and graphs and sound files via the DiagnosticableAspect, increasing complexity.Diagnosticable, or you’re out of luck.debuggingAspects field. There’s no lint warning.Compare this with the LINT.IfChange approach:
class TroutMissile implements Diagnosticable {
// LINT.IfChange
Vector2 velocity;
Vector2 acceleration;
// LINT.ThenChange(//src/debugging/modal.dart)
// ...
}
The linked Dart file can be completely separate, can import whatever it wants (including Flutter, for example), and can make all the UI decisions it wants. And. you get a lint warning.
Yep, this example is contrived — but you get the picture.
I’m sure there are similar cross-language lints out there, I’m just not aware of them being used. I think it’s a shame.
I realize the lint’s approach may seem dirty. It’s literally just a comment with a “special” meaning. There’s no cleverness here. You’re throwing static analysis out of the window for what? For searching a string in the codebase?
Look — I’m as much fond of elegant solutions as the next person, but I also like stupidly simple, practical solutions. And I like this one.
I advocate for creating a new open source tool that has the exact same syntax as Google’s LINT.IfChange. Google is unlikely to release that code to open source because I think LINT.IfChange is too intricately part of the company’s source control infrastructure. Also, it’s not like people outside Google are clamoring for tiny lint tools to be released.
A naive implementation of LINT.IfChange shouldn’t take more than a couple of hours, especially if you already have a library that can talk to git (example). But the devil’s in the details. Since the library needs to work across programming languages, you might have to be able to find out what is a comment in what file. Or maybe you just search for “LINT.IfChange” and “LINT.ThenChange(...)” since those strings are quite unique — and keep your fingers crossed that those don’t mean something else? What happens when the linked file doesn’t exist? What if it’s being renamed in this commit? Do you want to only support paths in ThenChange or also (the less used) labels? Should the tool be compiled to a binary (probably yes) and if so, how do you distribute it?
You get the picture.
Then add to it the terrifying possibility that the lint tool actually becomes widely popular. Now you have the responsibility (though only felt, not contractual) to keep this tool maintained and running for years. Inevitably, there will be bugs and feature requests.
Once again, you get the picture.
I really wanted to work on this myself but, given my schedule for the coming many months, I shouldn’t take on more work. But, if you — despite all those caveats I've outlined above — are in a mood for a side project, I’ll be among your first users.
— Filip Hráček
November 2025