A linter for XSL/XSLT stylesheets. It first checks that every stylesheet is well-formed and every XPath expression compiles, then flags stylistic, semantic, and logical mistakes — each pinpointed to its exact line and column, with a fix where the correction is unambiguous. Think of it as the checks ESLint gives JavaScript, but for XSL.
npx @maxonfjvipon/xslint path/to/stylesheets
It also runs in your editor — VS Code and compatible editors and JetBrains IDEs — in CI via the GitHub Action, or embedded through the language server.
[ERROR] sheet.xsl(2:1) The xsl:output instruction is missing. (not-using-output)
[WARNING] sheet.xsl(3:3) The match attribute starts with //, which scans the whole tree. (starts-with-double-slash)
[WARNING] sheet.xsl(4:5) A variable has a single-character name. (short-names)
Pointed at core stylesheets from the three most widely-used XSLT projects
— DocBook-XSL
(1.0), TEI (2.0), and
DITA-OT (1.0/2.0), 70 files
in all — xslint surfaced 2,191 findings across 25 different checks, with
no false positives from its validators: 106 xsl:choose blocks
with no xsl:otherwise, 71 stylesheet functions never called, 67
unused named templates, and more. Real stylistic and logical findings in code
that has shipped for decades.
49 checks, each with its rationale:
| Check | Type | Severity | Description |
|---|---|---|---|
| blank-nested-if | xpath | warning | A nested xsl:if inside another xsl:if is prohibited. Combine conditions with 'and' into a single xsl:if. |
| confusing-variable-and-node | xpath | warning | A variable name is used bare as a node selector. Use $name syntax to reference the variable. |
| duplicate-param-name | xpath | error | Two xsl:param siblings share a @name. Each parameter of a template, function, or stylesheet must have a distinct name. |
| duplicate-with-param-name | xpath | error | A single call passes two xsl:with-param with the same @name. Each parameter can be supplied only once. |
| empty-content-in-instructions | xpath | warning | An instruction element such as xsl:for-each or xsl:if has no content. Add content or remove the empty element. |
| empty-variable | xpath | warning | An empty xsl:variable binds the empty string for no reason. Give it a @select or some content, or remove it. |
| function-template-complexity | xpath | warning | The function or template contains more than 50 XSLT elements. Split it into smaller, focused units. |
| function-template-is-not-child-of-stylesheet | xpath | error | 'xsl:function' or 'xsl:template' are used in the wrong places. Use 'xsl:function' and 'xsl:template' as child nodes of 'xsl:stylesheet'. |
| function-use-in-xslt-1 | xpath | error | 'xsl:function' is declared in XSLT version 1.0. Use a named template or upgrade the stylesheet version to 2.0 instead. |
| incorrect-use-of-boolean-constants | xpath | warning | The test is the string 'true' or 'false', a non-empty string that is always true, not a boolean. Use true() or false() instead. |
| missing-id-in-stylesheet | xpath | warning | The '@id' attribute is missing in the 'xsl:stylesheet' element. Declare it to specify the unique identifier explicitly. |
| missing-version-in-stylesheet | xpath | warning | The '@version' attribute is missing in the 'xsl:stylesheet' element. Declare it to specify the stylesheet version explicitly. |
| mode-or-priority-without-match | xpath | error | An xsl:template that has no match attribute must have no mode attribute and no priority attribute. Add match attribute or remove mode or priority. |
| monolithic-design | xpath | warning | The stylesheet contains only one template or function. Extract logic into separate, focused templates. |
| name-starts-with-numeric | xpath | warning | A variable, function, or template name starts with a digit, which is invalid in XPath. Rename it to start with a letter. |
| not-creating-element-correctly | xpath | warning | xsl:element is used with a static name. Use a literal result element instead. |
| not-using-attribute-test-in-if-when-node | xpath | error | 'xsl:if' or 'xsl:when' don't have attribute @test. Add this attribute or delete node. |
| not-using-output | xpath | error | The xsl:output instruction is missing. Declare it to specify the serialization format explicitly. |
| not-using-schema-types | xpath | warning | No built-in Schema types are used in XSLT 2.0 or 3.0 mode. Declare variable types with xs:string, xs:integer, or similar. |
| null-output-from-stylesheet | xpath | warning | The root template contains only variable declarations and produces no output. Add instructions that write to the result tree. |
| output-method-xml | xpath | warning | xsl:output declares method='xml' while the root template generates HTML elements. Change the method to 'html'. |
| setting-value-of-variable-incorrectly | xpath | warning | A variable is assigned via a nested xsl:value-of instead of the select attribute. Use select syntax instead. |
| short-names | xpath | warning | A variable, function, or template has a single-character name. Use a descriptive name that reveals intent. |
| starts-with-double-slash | xpath | warning | The match attribute of xsl:template starts with //, which scans the entire document tree. Use a more specific pattern. |
| stylesheet-has-no-templates | xpath | error | The stylesheet has no templates, functions, or variables and offers nothing. Add at least one template. |
| template-has-no-name-or-match | xpath | error | An 'xsl:template' must have a '@match' or '@name' attribute, or both. Add one of them. |
| text-outside-xsl-text | xpath | warning | Literal text appears directly inside an 'xsl:' instruction element. Wrap it in 'xsl:text'. |
| too-many-small-templates | xpath | warning | The stylesheet has 10 or more templates with fewer than 3 child elements. Merge related small templates to reduce fragmentation. |
| unsorted-imports | xpath | warning | The 'xsl:import' elements are not sorted alphabetically by '@href'. Sort imports to improve readability. |
| unused-function-template-parameter | xpath | warning | A parameter is declared but never referenced in the function or template body. Remove it or use it. |
| use-choose-without-otherwise | xpath | warning | xsl:choose has no xsl:otherwise branch. Add xsl:otherwise to handle unmatched cases explicitly. |
| use-double-slash | xpath | warning | The match attribute of xsl:template contains //, which triggers a full document scan. Use a more specific path. |
| use-single-option-for-choose | xpath | warning | xsl:choose contains only one xsl:when branch. Replace it with xsl:if. |
| using-disable-output-escaping | xpath | warning | disable-output-escaping='yes' is set, which produces implementation-defined output. Use xsl:copy-of or literal elements instead. |
| using-namespace-axis | xpath | warning | The namespace:: axis is deprecated in XSLT 2.0. Use fn:in-scope-prefixes() and fn:namespace-uri-for-prefix() instead. |
| using-not-outermost-stylesheet | xpath | error | xsl:stylesheet is nested inside another element. It must be the outermost element of the document. |
| variable-or-param-with-select-and-content | xpath | error | An xsl:variable or xsl:param has both a select attribute and a non-empty body. Use only one way to set the value. |
| variable-or-param-without-name | xpath | error | xsl:variable and xsl:param require a @name attribute. Add one or remove the element. |
| with-param-use-in-invalid-parent-node | xpath | error | xsl:with-param is allowed only within xsl:call-template, xsl:apply-templates, xsl:apply-imports, xsl:next-match and xsl:next-iteration. |
| with-param-without-name | xpath | error | xsl:with-param requires a @name attribute to bind the parameter it passes. Add one. |
| unused-function | corpus | warning | A stylesheet function is never called in any expression across the corpus. Remove it or call it. |
| unused-named-template | corpus | warning | A named template is never invoked via xsl:call-template. Remove it or call it. |
| unused-variable | corpus | warning | A variable is declared but never referenced by $name across the corpus. Remove it or use it. |
| invalid-xpath-expression | validation | error | An XPath expression is malformed and cannot be parsed. Fix its syntax. |
| malformed-stylesheet | validation | error | The stylesheet is not well-formed XML and cannot be parsed. Fix its syntax. |
| redundant-namespace-declarations | format | warning | The xsl:stylesheet declares a namespace prefix that is never used. Remove the redundant xmlns declaration. |
| redundant-whitespace | format | warning | An XPath expression has redundant whitespace. Remove the extra spaces. |
| unabbreviated-axis | format | warning | Verbose axis specifiers child::, attribute::, or parent::node() are used. Replace them with the node name alone, @, or .. respectively. |
| use-node-set-extension | format | warning | The node-set() extension function is unnecessary in XSLT 2.0 and later. Query the variable directly without it. |