todos
is a fast unixy CLI
tool that searches for TODO comments in code and prints them in various
formats.
See the FAQ for more info on the philosophy behind the project.
- Easily installed, single static binary.
- 50+ supported languages.
- TODO comment label and message parsing.
- JSON output format.
.gitignore
, vendored code, generated code, and VCS file aware.- GitHub Actions integration.
- Editor integration via
efm-langserver
. - Git blame support (experimental).
- Character set detection.
todos
can be installed via multiple methods.
Download the slsa-verifier
and verify it's checksum:
curl -sSLo slsa-verifier https://github.com/slsa-framework/slsa-verifier/releases/download/v2.6.0/slsa-verifier-linux-amd64 && \
echo "1c9c0d6a272063f3def6d233fa3372adbaff1f5a3480611a07c744e73246b62d slsa-verifier" | sha256sum -c - && \
chmod +x slsa-verifier
Download and verify the todos
binary and verify it's provenance:
curl -sSLo todos https://github.com/ianlewis/todos/releases/download/v0.13.0/todos-linux-amd64 && \
curl -sSLo todos.intoto.jsonl https://github.com/ianlewis/todos/releases/download/v0.13.0/todos-linux-amd64.intoto.jsonl && \
./slsa-verifier verify-artifact todos --provenance-path todos.intoto.jsonl --source-uri github.com/ianlewis/todos --source-tag v0.13.0 && \
chmod +x todos && \
cp todos ~/bin/
Install todos
by installing the @ianlewis/todos
package from npm
:
npm install -g @ianlewis/todos
Verify the package signature:
npm audit signatures
Download and run todos
with Docker. You should use a specific version tag:
docker run --rm -t -v $(pwd):/src ghcr.io/ianlewis/todos:v0.13.0 /src
Verify the image attestation using
cosign
:
cosign verify-attestation \
--type slsaprovenance \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp '^https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v[0-9]+.[0-9]+.[0-9]+$' \
--certificate-github-workflow-repository "ianlewis/todos" \
--certificate-github-workflow-ref "refs/tags/v0.13.0" \
ghcr.io/ianlewis/todos:v0.13.0
todos
is available as an Aqua package. You can
install it with the aqua
CLI tool:
# Initialize Aqua configuration if you haven't already.
aqua init
# Add todos to your aqua.yaml.
aqua generate ianlewis/todos
If you already have Go 1.20+ you can install the latest version using go install
:
go install github.com/ianlewis/todos/cmd/todos
You can also install todos
from source as a Go tool dependency.
go get -tool github.com/ianlewis/todos/cmd/todos
This will allow you to use todos
in your project using the go tool
command.
go tool github.com/ianlewis/todos/cmd/todos
Be aware though that there are a few downsides to using the Go tools approach.
- This will compile
todos
locally and using your local Go version. - The dependencies used to compile
todos
may be different than those used to compile its releases and thus is not guaranteed to work. - It allows installation from the main branch, which may not be stable.
- It's slower than binary installation.
Simply running todos
will search TODO comments starting in the current
directory.Here is an example running in a checkout of the
Kubernetes
codebase.
kubernetes$ todos
build/common.sh:346:# TODO: remove when 17.06.0 is not relevant anymore
build/lib/release.sh:148:# TODO: Docker images here
cluster/addons/addon-manager/kube-addons.sh:233:# TODO: Remove the first command in future release.
cluster/addons/calico-policy-controller/ipamblock-crd.yaml:41:# TODO: This nullable is manually added in. We should update controller-gen
cluster/addons/dns/kube-dns/kube-dns.yaml.base:119:# TODO: Set memory limits when we've profiled the container for large
...
See the use cases for more examples of how to use todos
.
todos
supports a wide variety of languages and file formats. It detects the
language of a file based on its extension, shebang line, or the contents of the
file.
See SUPPORTED_LANGUAGES.md
for a full list of supported languages and
comment types.
"TODO" comments are comments in code that mark a task that is intended to be done in the future.
For example:
// TODO(label): message text
For TODO comments to be more easily parsed keep in mind the following:
- Spaces between the comment start and 'TODO' is optional
(e.g.
//TODO: some comment
) - TODOs should have a colon if a message is present so it can be distinguished from normal comments.
- TODOs can be prefixed with
@
(e.g.// @TODO: comment
) - Comments can be on the same line with other code
(e.g.
x = f() // TODO: call f
) - Line comment start sequences can be repeated (e.g.
//// TODO: some comment
) - TODO comments can be in multi-line comments, however only the single line where the TODO occurs is printed for multi-line comments.
There a few variants of this type of comment that are in wide use.
TODO
: A general TODO comment indicating something that is to be done in the future.FIXME
: Something that is broken that needs to be fixed in the code.BUG
: A bug in the code that needs to be fixed.HACK
: This code is a "hack"; a hard to understand or brittle piece of code. It could use a cleanup.XXX
: Danger! Similar to "HACK". Modifying this code is dangerous. ItCOMBAK
: Something you should "come back" to.
TODO
,FIXME
,BUG
,HACK
,XXX
,COMBAK
are supported by default. You can
change this with the --todo-types
flag.
TODO comments can include some optional metadata. Here are some examples:
-
A naked TODO comment.
// TODO
-
A TODO comment with an explanation message
// TODO: Do something.
-
A TODO comment with a linked bug or issue and optional message
// TODO(github.com/ianlewis/todos/issues/8): Do something.
-
A TODO comment with a username and optional message. This type is discouraged as it links the issue to a specific developer but can be helpful temporarily when making changes to a PR. Linking to issues is recommended for permanent comments.
// TODO(ianlewis): Do something.
Tracking TODOs in code can help you have a cleaner and healthier code base. Here are some basic use cases.
You can use the todos
to find TODO comments in your code and print them out.
Running it will search the directory tree starting at the current directory by
default. By default, it ignores files that are in "VCS" directories (such
as.git
or .hg
) and vendored code (such as node_modules
, vendor
, and
third_party
).
$ todos
main.go:27:// TODO(#123): Return a proper exit code.
main.go:28:// TODO(ianlewis): Implement the main method.
You can run todos
on sub-directories or individual files by passing them on
the command line.
kubernetes$ todos hack/ Makefile
hack/e2e-internal/e2e-cluster-size.sh:32:#TODO(colhom): spec and implement federated version of this
hack/ginkgo-e2e.sh:118:# TODO(kubernetes/test-infra#3330): Allow NODE_INSTANCE_GROUP to be
hack/lib/golang.sh:456:# TODO: This symlink should be relative.
hack/lib/protoc.sh:119:# TODO: switch to universal binary when updating to 3.20+
hack/lib/util.sh:337:# TODO(lavalamp): Simplify this by moving pkg/api/v1 and splitting pkg/api,
...
todos
can produce output in JSON format for more complicated processing.
kubernetes$ todos -o json
{"path":"build/common.sh","type":"TODO","text":"# TODO: remove when 17.06.0 is not relevant anymore","label":"","message":"remove when 17.06.0 is not relevant anymore","line":346,"comment_line":346}
{"path":"build/lib/release.sh","type":"TODO","text":"# TODO: Docker images here","label":"","message":"Docker images here","line":148,"comment_line":148}
{"path":"cluster/addons/addon-manager/kube-addons.sh","type":"TODO","text":"# TODO: Remove the first command in future release.","label":"","message":"Remove the first command in future release.","line":233,"comment_line":233}
{"path":"cluster/addons/calico-policy-controller/ipamblock-crd.yaml","type":"TODO","text":"# TODO: This nullable is manually added in. We should update controller-gen","label":"","message":"This nullable is manually added in. We should update controller-gen","line":41,"comment_line":41}
{"path":"cluster/addons/dns/kube-dns/kube-dns.yaml.base","type":"TODO","text":"# TODO: Set memory limits when we've profiled the container for large","label":"","message":"Set memory limits when we've profiled the container for large","line":119,"comment_line":119}
...
kubernetes$ # Get all the unique files with TODOs that Tim Hockin owns.
kubernetes$ todos -o json | jq -r '. | select(.label = "thockin") | .path' | uniq
You can use the --blame
flag to find the author of a TODO comment. This will
use git blame
to find the latest author of the line where the TODO comment
occurs. However, this can be slow for large repositories
(#1519).
$ todos --blame
.github/workflows/schedule.scorecard.yml:80:Ian Lewis <ian@ianlewis.org>:# TODO: Remove the next line for private repositories with GitHub Advanced Security.
.golangci.yml:172:Ian Lewis <ian@ianlewis.org>:# TODO(#1725): Reduce package average complexity.
Makefile:525:Ian Lewis <ian@ianlewis.org>:# TODO: remove when todos v0.13.0 is released. \
cmd/todos/app.go:42:Ian Lewis <ianlewis@google.com>:// TODO(github.com/urfave/cli/issues/1809): Remove init func when upstream bug is fixed.
internal/scanner/languages.go:37:Ian Lewis <ian@ianlewis.org>:// TODO(#1686): Refactor language config global variables.
internal/scanner/languages.go:143:Ian Lewis <ian@ianlewis.org>:// TODO(#1686): Refactor LanguagesConfig global variable.
...
You can use the output of todos
to create documentation of tasks.
For example, this creates a simple markdown document:
$ (
echo "# TODOs" && \
echo && \
todos --exclude-dir .venv --output json | \
jq -r 'if (.label | startswith("#")) then "- [ ] \(.label) \(.message)" else empty end' | \
uniq
) > todos.md
$ cat todos.md
# TODOs
- [ ] #1546 Support @moduledoc
- [ ] #96 Use a *Config
- [ ] #96 Use []*Comment and go-cmp
- [ ] #1627 Support OCaml nested comments.
- [ ] #1540 Read this closed string as a comment.
- [ ] #1545 Generate Go code rather than loading YAML at runtime.
If run as part of a GitHub action todos
will function much like a linter and
output GitHub workflow commands which will add check comments to PRs.
kubernetes$ todos -o github Makefile
::warning file=Makefile,line=313::# TODO(thockin): Remove this in v1.29.
::warning file=Makefile,line=504::#TODO: make EXCLUDE_TARGET auto-generated when there are other files in cmd/
An example workflow might look like the following. todos
will output GitHub
Actions workflow commands by default when running on GitHub Actions:
on:
pull_request:
branches: [main]
workflow_dispatch:
permissions: {}
jobs:
todos:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v3
- uses: slsa-framework/slsa-verifier/actions/installer@v2.7.0
- name: install todos
run: |
set -euo pipefail
curl -sSLo todos \
https://github.com/ianlewis/todos/releases/download/v0.13.0/todos-linux-amd64 && \
curl -sSLo todos.intoto.jsonl \
https://github.com/ianlewis/todos/releases/download/v0.13.0/todos-linux-amd64.intoto.jsonl && \
slsa-verifier verify-artifact todos \
--provenance-path todos.intoto.jsonl \
--source-uri github.com/ianlewis/todos \
--source-tag v0.13.0 && \
rm -f slsa-verifier && \
chmod +x todos
- name: run todos
run: |
./todos .
Sometimes issues get closed before all of the relevant code is updated. You can
use todos
to re-open issues where TODO comments that reference the issue
still exist in the code.
// TODO(#123): Still needs work.
See ianlewis/todo-issue-reopener
for more information.
You can use todos
with
efm-langserver
find and manage
TODOs in files opened in your favorite editor with LSP server support. This is
useful for quickly finding and jumping to TODOs in your code.
For example, in Neovim you can use the following configuration to show TODOs in
the quickfix window and jump to them. Install efm-langserver
(via Mason etc.)
and add the following to your LSP server Neovim configuration.
local lspconfig = require("lspconfig")
local todos = {
prefix = "todos",
lintCommand = "todos",
lintStdin = true,
lintIgnoreExitCode = true,
lintSeverity = 2, -- 2 = warning
lintFormats = {
"%f:%l:%m",
},
}
lspconfig.efm.setup({
settings = {
rootMarkers = { ".git/" },
languages = {
-- Add todos to each language.
sh = { todos },
bash = { todos },
conf = { todos },
gitignore = { todos },
-- Merge with any existing configuration.
html = { --[[ prettier, ]] todos },
css = { --[[ prettier, stylelint, ]] todos },
lua = { --[[ stylua, selene, ]] todos },
python = { todos },
rust = { todos },
go = { todos },
javascript = { --[[ prettier, ]] todos },
-- ...
},
}
})
pgilad/leasot
: A fairly robust tool with good integration with the Node.js ecosystem.judepereira/checktodo
: A GitHub PR checker that checks if PRs contain TODOs.kynikos/report-todo
: A generic reporting tool for TODOs.
Tracking TODOs in code can help you have a cleaner and healthier code base.
- It can help you realize when issues you thought were complete actually
require some additional work (See
ianlewis/todo-issue-reopener
). - It makes it easier for contributors to find areas of the code that need work.
- It makes it easier for contributors to find the relevant code for an issue.
grep
and rg
are amazing and very fast tools. However, there are a few
reasons why you might use todos
.
grep
andrg
don't have much knowledge of code and languages so it's difficult to differentiate between comments and code.todos
will ignore matches in code and only prints TODOs found it comments. It also ignores matches that occur in strings.grep
doesn't know about repository structure. It doesn't have inherent knowledge of VCS directories (e.g..git
) or vendored dependencies. It can't make use of.gitignore
or other hints.todos
can output JSON with parsed values from the TODOs. This gives users an easy way to search for TODOs with their username, or with a specific issue number.
See CONTRIBUTING.md
for contributor documentation.