Transforms, CoffeeScript Flavors, and Literate CoffeeScript
CoffeeLint normally lints plain CoffeeScript with the bundled CoffeeScript 2 compiler. Three features change what it sees: transforms that rewrite the source before linting, a custom compiler for alternative CoffeeScript dialects, and Literate CoffeeScript support. This page explains how each works and the trade-offs involved.
Transforms
Transforms were added in CoffeeLint 1.8, primarily so that projects using JSX-flavored CoffeeScript (for React) could be linted. A transform is a function that takes the source text and returns new source text. CoffeeLint runs all configured transforms, in order, before any rule sees the code.
Configuring Transforms
Transforms are listed in the special coffeelint section of your configuration:
{
"coffeelint": {
"transforms": ["coffee-react-transform"]
}
}
Each entry is a module name that is loaded with require. Install the module in your project:
npm install --save-dev coffee-react-transform
Writing a Transform
A transform module exports a single function:
# replace-build-placeholders.coffee
# Turns build-time placeholders such as <%= VERSION %> into string
# literals, so the file parses, without changing the number of lines.
module.exports = (source) ->
source.replace /<%=\s*(\w+)\s*%>/g, (match, name) -> "'#{name}'"
Rules:
- The function receives the whole file as one string and must return a string.
- It should keep every line where it was. Never add or remove lines if you can avoid it.
- It should be fast and side-effect free, since it runs for every linted file.
How Transform Modules Are Resolved
- Discovered configuration (no
-f): each name is resolved to a full path relative to the directory of the file being linted, using Node's module resolution and the extensions.js,.coffee,.litcoffee, and.coffee.md. Package names work well because resolution walks up to your project'snode_modules. Relative paths such as./tools/transform.coffee, however, are interpreted relative to each linted file's directory, so they only work for files in one folder. - Explicit configuration (
-f) and the API: names are resolved when linting, from the current directory first, then with a plainrequire, then as a path relative to the current directory.
For reliable results, publish shared transforms as packages (or reference them with absolute paths).
Caveats
- Failures are silent. If a transform module cannot be loaded or throws, CoffeeLint skips it without any message and lints the source as it was before that transform. If a transform seems to have no effect, test that it loads with
node -p "require('module-name')". - Transformed code is what gets linted. CoffeeLint cannot control what transforms produce, and the output may violate your rules. Use inline directives such as
# coffeelint: disable=max_line_lengthwhere needed. - Line numbers may shift. If a transform changes the number of lines, CoffeeLint emits the
transform_messes_up_line_numberswarning, because every reported line number in that file is probably wrong. The check only compares totals, so a transform that adds a line in one place and removes one elsewhere goes unnoticed. If line numbers are off, contact the transform's maintainers about preserving lines. - Not available in the browser. Transforms are loaded with Node's module system and are skipped in the browser build.
Custom CoffeeScript Compilers (Flavors)
CoffeeLint 1.8 also added a way to replace the bundled compiler with another module, for dialects such as IcedCoffeeScript:
{
"coffeelint": {
"coffeescript": "iced-coffee-script"
}
}
The module is resolved like transforms. It replaces the compiler used to tokenize and parse your code, so token-based and AST-based rules operate on the dialect's output.
The project's own documentation calls this functionality "basically unsupported": if a flavor breaks things, it is up to you to work with that flavor's maintainers to align it with the official CoffeeScript. Known issue: IcedCoffeeScript breaks the cyclomatic_complexity rule.
Two implementation details of version 2.1.0 to be aware of when using this option:
- The replacement compiler is stored in a module-level variable. Once a file configured with a custom compiler has been linted, that compiler stays in effect for every later file linted in the same process.
- In the source, the compiler module is loaded through a helper that is only initialized while loading
transforms. If configuringcoffeescripton its own fails with aTypeErrorabout readingrequireofundefined, that is the cause; configuring at least one transform alongside it avoids the problem.
Literate CoffeeScript
Literate CoffeeScript files (.litcoffee, .coffee.md) are Markdown documents whose indented blocks are code. CoffeeLint handles them by converting them into regular CoffeeScript before linting:
- CoffeeScript's own helper turns the Markdown prose into
#comment lines. - CoffeeLint then removes the first four spaces (or one tab) from every line, so code returns to the indentation it would have in a normal
.coffeefile. - All rules run on the result, so line numbers still match the original file.
Special handling:
max_line_lengthsubtracts the two characters of the#prefix added to prose lines, so prose is measured at its real length.- Line rules can call
lineApi.isLiterate()to adapt their behavior. See Writing Custom Rules.
How files are recognized:
- Files passed explicitly are treated as literate based on their extension.
- Directories only yield
*.coffeefiles by default; add--ext litcoffeeto include Literate CoffeeScript. - For standard input, pass
--literate.
coffeelint --ext litcoffee src docs
cat guide.litcoffee | coffeelint --stdin --literate
From the API, pass true as the third argument to coffeelint.lint(source, config, literate).