Skip to content

/flags/ruby/ruby-c-syntax-check

ruby -c file.rb: check Ruby syntax without running the script

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash)updated
tool
Ruby
command
ruby
flag
-c
default
off (the script is executed)

ruby -c file.rb parses the file and reports syntax errors without running it. A valid file prints Syntax OK and exits with status 0; a broken one prints the error with its line and column and exits with status 1. It is a cheap pre-commit or CI check, and it works on any file that contains Ruby code, including Rakefiles, Gemfiles and .gemspec files.

Valid and broken file

The example runs the official CRuby 3.4 build for WebAssembly from the ruby.wasm project, as a regular command-line program with Node.js; ruby below is that interpreter:

bash
curl -sSfLO https://github.com/ruby/ruby.wasm/releases/download/2.8.1/ruby-3.4-wasm32-unknown-wasip1-full.tar.gz
echo "ccda86a375a4fe09849846d3b03a370172a4902a0c571087f48457388a2762c7  ruby-3.4-wasm32-unknown-wasip1-full.tar.gz" | sha256sum -c --quiet
tar xzf ruby-3.4-wasm32-unknown-wasip1-full.tar.gz
cat > ruby.mjs <<'JS'
// Minimal "ruby" command: runs the official CRuby 3.4 WASI build with the given arguments
import fs from "node:fs";
import { WASI } from "node:wasi";
const root = "ruby-3.4-wasm32-unknown-wasip1-full/usr";
const wasi = new WASI({ version: "preview1", args: ["ruby", "--disable-gems", ...process.argv.slice(2)], env: {}, preopens: { "/usr": root, ".": "." }, returnOnExit: true });
const mod = await WebAssembly.compile(fs.readFileSync(`${root}/local/bin/ruby`));
process.exitCode = wasi.start(await WebAssembly.instantiate(mod, wasi.getImportObject()));
JS
ruby() { node --no-warnings ruby.mjs "$@"; }
cat > good.rb <<'EOF'
def total(items)
  items.sum { |i| i[:price] }
end
puts total([{ price: 2 }, { price: 3 }])
EOF
cat > bad.rb <<'EOF'
def total(items)
  items.sum { |i| i[:price]
end
EOF
ruby -c good.rb; echo "exit: $?"
ruby -c bad.rb; echo "exit: $?"
echo 'puts "only checked"' | ruby -c; echo "exit: $?"
echo 'BEGIN { puts "BEGIN ran" }' | ruby -c

Output

Syntax OK
exit: 0
ruby: bad.rb:3: syntax errors found (SyntaxError)
  1 | def total(items)
  2 |   items.sum { |i| i[:price]
> 3 | end
    | ^ expected a block beginning with `{` to end with `}`
    | ^~~ unexpected 'end', assuming it is closing the parent method definition

exit: 1
Syntax OK
exit: 0
Syntax OK

good.rb is not executed: nothing is printed except Syntax OK. For bad.rb, Ruby 3.4's Prism parser points at the missing closing brace and shows the offending lines. Code on standard input is checked too, and even a BEGIN block is not run.

What -c catches and what it does not

ProblemFound by ruby -c?
Missing end, unbalanced braces or bracketsyes
Unterminated strings, heredocs, regular expressionsyes
Invalid tokens, misplaced keywordsyes
Undefined method or variable (NoMethodError, NameError)no, run time only
Missing constants or files (NameError, LoadError)no, run time only
Wrong number of argumentsno, run time only
Warnings such as unused variablesonly with -w as well: ruby -wc file.rb

Errors like undefined method for nil and uninitialized constant need the code to run, so tests are still required.

Checking a whole project

bash
# every Ruby file below lib/ and app/, stop at the first failure
find lib app -name '*.rb' -print0 | xargs -0 -n1 ruby -c > /dev/null

# with warnings too
ruby -wc lib/my_gem.rb

ruby -c accepts one script per run, hence -n1. Redirecting standard output hides the Syntax OK lines; errors go to standard error and still appear.

Pitfalls

  • The parser changed in Ruby 3.4 (Prism became the default), so the wording and layout of syntax errors differ from Ruby 3.3 and earlier, which printed syntax error, unexpected end-of-input. Scripts that grep the message should only rely on the exit status.
  • ruby -c checks against the syntax of the Ruby version you run. Code that uses newer syntax (for example it in blocks, Ruby 3.4) can pass on one version and fail on another; check with the version you deploy.
  • Combine -w and -c to see parse-time warnings, such as unused variables, as covered in ruby -w.

FAQ

Does ruby -c find undefined methods or missing constants?

No. It only parses the file. Errors such as undefined method for nil or uninitialized constant happen when the code runs, so use tests, RuboCop or a type checker (Sorbet, Steep) for those.

Can ruby -c check ERB templates?

Not directly, because ERB is not Ruby syntax. Compile the template to Ruby first: erb -x -T - template.erb | ruby -c.

Does ruby -c execute BEGIN blocks or require files?

It does not run the program, so BEGIN blocks and require calls inside the file are not executed. Libraries given on the command line with -r are loaded before the check, so avoid -r with -c if they have side effects.

Sources