You focus on syntax changes to a language. Syntax changes are fairly easy to pull off in a backwards-compatible manner:
The new syntax would have been illegal in previous versions. This is why new language features often reuse keywords or introduce contextual keywords.
The new features have to be enabled explicitly, either as a feature switch in the source code (like Perl's
use v5.22 or
use feature 'state'), or as a compiler option (like
g++ --std=c++14 or
javac -source 1.8). Both of these effectively put the transpiler into the compiler frontend itself, thus avoiding the need for an extra tool.
If a language is not yet very stable, a source conversion tool is more desirable than supporting old syntax versions in the compiler. For Golang,
go fix is a tool that can rewrite code to reflect syntax and API changes. But in the long run, stability is more important: while you might not do this every day, it is important that even decades-old code can still be executed without extra hassle. Backwards-incompatible platform changes may be necessary, but explain why some organizations need to keep some DOS or Windows XP machines around…
Some language changes do not affect the syntax, but the semantics. Rewriting code to reflect semantic changes might be possible in some simple cases, but is impossible in the general case: you quickly run into the halting problem.
To aid the Python 2 to Python 3 transition, a
2to3 source converter was written. With this tool, there is no expectation that it will always work without intervention. Simple updates like rewriting print statements as print functions are of course no problem. But the changes to the numeric types (
long was folded into
int, which is no longer fixed-size, and the
/ operator now always produces floats) are already more difficult – remember that Python is dynamically typed. Non-trivial uses of metaprogramming are entirely hopeless. The lesson here isn't that you shouldn't write such tools, but that tools are very limited in what they can do.
For libraries and some applications, it is often important that they can be used with multiple language versions. If we had to update the source code to use a newer language version, this would prevent it from working on other language versions as well. Such programs must be written in the common subset of all language versions. This is not hypothetical, but e.g. common in C++ (where you maybe want your header files to be legal C code, or want to support multiple C++ standards), or in applications for interpreted languages like Perl, Python, PHP where you don't want to force your users to upgrade (they often can't upgrade easily).
Breaking language changes are then only OK if you have control over all aspects of deployment, and have the time to verify the changes made by some conversion tool. Otherwise, users are strongly incentivized to avoid upgrading to the new language version: breaking changes are highly disruptive, and are a strong indicator that the language is not yet fit for serious development.