@maxonfjvipon/xslint
v0.0.10
Published
XSL Linter
Downloads
591
Readme
xslint
Lint your XSL/XSLT stylesheets — catch malformed XML, invalid XPath, and stylistic defects before they ship.
xslint is a CLI linter for XSL stylesheets. It first checks that every
stylesheet is well-formed and every XPath expression compiles, then runs its
checks for stylistic, semantic, and logical problems — each reported with its
exact line and column, in your terminal or in CI.
Quick start
Run it on your stylesheets — no install needed:
npx @maxonfjvipon/[email protected] path/to/stylesheetsGiven a stylesheet like this:
<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="//book">
<xsl:variable name="x" select="title"/>
<xsl:value-of select="$x"/>
</xsl:template>
</xsl:stylesheet>xslint points at each problem with its exact position and how to fix it:
[ERROR] sheet.xsl(2:1) The xsl:output instruction is missing. Declare it to specify the serialization format explicitly. (not-using-output)
[WARNING] sheet.xsl(3:3) The match attribute of xsl:template starts with //, which scans the entire document tree. Use a more specific pattern. (starts-with-double-slash)
[WARNING] sheet.xsl(4:5) A variable, function, or template has a single-character name. Use a descriptive name that reveals intent. (short-names)In CI, use the GitHub Action to get inline annotations on your pull requests:
- uses: actions/checkout@v6
- uses: maxonfjvipon/[email protected]Browse the full check catalog.
Installation
To install xslint globally, install npm first, then run:
npm install -g @maxonfjvipon/[email protected]
xslint --versionBuild
To build xslint from source, clone this repository:
git clone [email protected]:maxonfjvipon/xslint.git
cd xslintNext, run these commands to install xslint system-wide:
npm install
npm install -g .Verify that xslint is installed correctly:
$ xslint --version
0.0.0Usage
You can check all files in current directory:
xslintTo check specified files - provide them as arguments:
xslint path/to/your/file1.xsl path/to/your/file2.xslYou can suppress some checks by using --suppress option:
xslint --suppress=confusing-variable-and-nodeYou can skip several checks at once if they contain a certain substring:
xslint --suppress=unusedIf you want to suppress many checks, use --suppress as many times as you need:
xslint --suppress=monolithic-design --suppress=short-namesConfiguration
Project-wide settings live in a .xslint.yml file, discovered by walking up
from the current directory (or passed with --config <path>). Command-line
flags override the file, and the file overrides the built-in defaults.
# .xslint.yml
rules:
short-names: off # turn one check off
"unused-*": error # or a family, by glob
exclude:
- "test/**" # globs to skip, relative to this file
max-warnings: 10 # default for --max-warnings
log-level: info # default for --log-level
quiet: false # default for --quietrulesmaps a check name — or a glob such asunused-*— tooff,warning, orerror.offdisables the check (like--suppress);warninganderrorre-grade its severity.excludelists globs, relative to the config file's own directory, whose matching files are not linted.max-warnings,log-level, andquietset the defaults for the matching command-line flags.
Unknown top-level keys, rule names that match no check, and values of the wrong
type (a non-numeric max-warnings, a non-list exclude, a non-boolean
quiet, a non-string log-level) are reported and ignored, so typos do not
pass silently.
Inline suppression
Silence a rule in one place with an XML-comment directive. Rule names are optional and space-separated; with none, every rule at that location is suppressed.
<!-- xslint-disable-next-line short-names -->
<xsl:variable name="x" select="1"/>
<!-- xslint-disable-file not-using-schema-types -->xslint-disable-next-line [rules]— the line after the comment.xslint-disable-line [rules]— the comment's own line.xslint-disable-file [rules]— the whole file (put it near the top).
A directive that suppresses nothing is reported as unused, so stale ones can be found and removed.
Output
Defects are written to stdout; progress and diagnostic logs go to stderr, so
xslint path/to/dir > report.txt captures only the findings. Pass --quiet to
drop the informational log lines:
xslint --quietOutput is colored only when it goes to an interactive terminal, so a redirected
or piped run stays plain text; setting the conventional NO_COLOR environment
variable turns coloring off everywhere.
Machine-readable output
--format selects the output. text (the default) is the human format above;
json and sarif print a single document to stdout — logs stay on stderr, so
the document is clean to pipe or redirect; github prints GitHub Actions
workflow commands:
xslint --format json path/to/dir # a flat array of defects
xslint --format sarif path/to/dir # a SARIF 2.1.0 log
xslint --format github path/to/dir # ::warning/::error annotations for CIInside a GitHub Action, --format github makes each defect an inline
annotation on the pull-request diff with no upload step — the lowest-friction
way to see findings on a review.
SARIF feeds GitHub code scanning, so xslint findings appear as annotations on pull requests:
- run: xslint --format sarif . > xslint.sarif || true
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: xslint.sarifThe || true keeps a findings exit code from failing the step before the
upload; the alerts still surface in code scanning. Run it from the repository
root so the reported paths stay repo-relative — a file outside the working
directory is named by its absolute path instead.
Fixing
Some defects have a single unambiguous correction, and --fix applies it in
place:
xslint --fix path/to/dirToday this covers four checks:
redundant-whitespace— a doubled space is collapsed to one, and a space leading or trailing an XPath expression is removed.unabbreviated-axis— a verbose axis specifier is shortened:child::xbecomesx,attribute::xbecomes@x, andparent::node()becomes...redundant-namespace-declarations— a namespace prefix declared on the stylesheet but never used is deleted.use-node-set-extension— the redundantnode-set()extension is unwrapped in XSLT 2.0 and later:exsl:node-set($x)becomes$x.
Only the exact span that was flagged is rewritten — the rest of the file is left byte-for-byte intact — and a fix is skipped rather than applied when the source no longer matches what it expects.
Other defects have a correction that is clear but opinionated — it changes
behavior, removes code, or is one of several reasonable choices. Those are
offered as suggestions, applied only with --fix-suggestions, never
silently by --fix:
xslint --fix-suggestions path/to/dirToday this covers:
using-disable-output-escaping— the attribute is removed, which changes how the output is escaped.output-method-xml—method="xml"becomes"html"when the stylesheet emits HTML.missing-version-in-stylesheet—version="1.0"is declared (a guess you may want to change).mode-or-priority-without-match— the orphanmodeorpriorityattribute is removed.
Checks whose correction needs real judgment (a fresh name, a more specific
path) stay report-only. A run without --fix reports how many defects each
option would fix.
Pass --fix-dry-run to see what would remain after fixing, without writing any
file:
xslint --fix-dry-run path/to/dirExit code
xslint exits non-zero when any error-severity defect is found. Warnings do
not fail the run by default; to make them count, cap the allowed number with
--max-warnings:
xslint --max-warnings=0 # any warning fails the run
xslint --max-warnings=10 # more than ten warnings fails the runChecks
The full list of checks with descriptions and examples is available at maxonfjvipon.github.io/xslint.
xslint runs in two stages. Validators first establish that the input is valid; linters then run over the stylesheets that pass, catching stylistic, semantic, and logical problems. A stylesheet that does not parse is reported once and skipped, so one broken file never hides the feedback on the rest.
Validators:
- XML well-formedness — a stylesheet that is not well-formed XML is reported and excluded from linting.
- XPath syntax — every bare XPath expression (in
select,test,use,value,group-by,group-adjacent, and the XSLT 3.0key,initial-value,xpath,context-item,with-params,namespace-context) is parsed; the ones the processor cannot parse are reported.
Linters:
- Per-file checks evaluate one stylesheet at a time (most checks).
- Cross-file checks reason across all the stylesheets you lint together.
For example, a named template defined in one file but invoked from another
(via
xsl:import/xsl:include) is not reported as unused. Lint the whole project at once so these checks can see every caller. - Formatting checks read each XPath expression as a stream of tokens and flag stylistic noise — currently redundant whitespace (a doubled space, or a space leading or trailing the expression). Only expressions that already parse are checked, so a malformed one is reported once by the validator and never nagged about its spacing.
Programmatic use
xslint is embeddable — editors, build tools, and the forthcoming language
server import it instead of shelling out. lint takes in-memory sources and
returns the defects, touching no files and never exiting:
const {lint, fixed} = require('@maxonfjvipon/xslint')
const sources = [{file: 'sheet.xsl', content: '<xsl:stylesheet .../>'}]
const defects = lint(sources, {suppress: ['short-names']})
// each defect: {name, severity, message, file, line, pos, fix?}
// apply the fixable ones without writing to disk:
const {contents} = fixed(sources, defects)lint(sources, {suppress, overrides}) runs every validator and linter over the
{file, content} sources and honors inline xslint-disable directives;
fixed(sources, defects, suggestions) returns the rewritten content per file.
How to Contribute
Fork repository, make changes, then send us a pull request.
We will review your changes and apply them to the master branch shortly,
provided they don't violate our quality standards. To avoid frustration,
before sending us your pull request please make sure all your tests pass:
npm testNew linter rules live in src/resources/checks/xpath (per-file) or
src/resources/checks/corpus (cross-file), each with a matching test pack in
test/resources. The validators in src/resources/checks/validation and the
formatting checks in src/resources/checks/format are fixed in code; their
YAML only tunes severity and message. Regenerate the documentation site with
npx grunt docs.
You will need npm and node installed
See CONTRIBUTING.md for the full workflow and CHANGELOG.md for release notes.
