{"id":41989373,"url":"https://github.com/aurelia/template-lint","last_synced_at":"2026-01-26T00:29:41.974Z","repository":{"id":8787356,"uuid":"59676313","full_name":"aurelia/template-lint","owner":"aurelia","description":"Sanity check of Aurelia-flavor template HTML ","archived":false,"fork":false,"pushed_at":"2023-08-24T07:43:58.000Z","size":952,"stargazers_count":56,"open_issues_count":61,"forks_count":17,"subscribers_count":10,"default_branch":"develop","last_synced_at":"2024-05-01T09:51:28.129Z","etag":null,"topics":["aurelia","lint","template"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aurelia.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-05-25T15:44:02.000Z","updated_at":"2024-05-01T01:30:11.000Z","dependencies_parsed_at":"2023-01-11T20:11:19.571Z","dependency_job_id":null,"html_url":"https://github.com/aurelia/template-lint","commit_stats":null,"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"purl":"pkg:github/aurelia/template-lint","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aurelia%2Ftemplate-lint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aurelia%2Ftemplate-lint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aurelia%2Ftemplate-lint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aurelia%2Ftemplate-lint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aurelia","download_url":"https://codeload.github.com/aurelia/template-lint/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aurelia%2Ftemplate-lint/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28762527,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T23:06:19.311Z","status":"ssl_error","status_checked_at":"2026-01-25T23:03:50.555Z","response_time":113,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aurelia","lint","template"],"created_at":"2026-01-26T00:29:41.293Z","updated_at":"2026-01-26T00:29:41.968Z","avatar_url":"https://github.com/aurelia.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# aurelia-template-lint\n\nSanity check of Aurelia Templates\n\n![logo](https://d30y9cdsu7xlg0.cloudfront.net/png/30843-200.png)\n\n\n[![NPM version][npm-image]][npm-url]\n[![NPM downloads][npm-downloads]][npm-url]\n[![Travis Status][travis-image]][travis-url]\n[![Breaks-on][breaks-image]][npm-url]\n[![Stability][stability-image]][npm-url]\n[![Gitter][gitter-image]][gitter-url]\n\n## Info\n\nAurelia is a front-end platform built upon existing (or upcoming) industry standards.\nOne such standard is to ensure that all view-markup is parsable by regular browsers; while this allows Aurelia to forgo the need for a custom parser, it does mean poorly formatted html will be ignored without warning. \n\nThe goal of `aurelia-template-lint` is to detect problems with your template html and source before they become a problem in the browser, or are ignored by the browser entirely. \n\n## Install\n\n*Note: node.js 6 is required.*\n\n*Note: it is recommended you use this via the [gulp plugin](https://github.com/MeirionHughes/gulp-aurelia-template-lint). There is also a [webpack loader](https://www.npmjs.com/package/aurelia-template-lint-loader) available* \n\n*Note: If you use this library directly, in a production environment (ci), then ensure you lock to a minor version as this library is under development and subject to breaking changes on minor versions*\n\n\n```\nnpm install aurelia-template-lint --save-dev\n```\n\n## Usage\n\n```js\nconst AureliaLinter = require('aurelia-template-lint').AureliaLinter\n\nvar linter = new AureliaLinter();\n\nvar html = \"\u003ctemplate\u003e\u003c/template\u003e\"\n\nlinter.lint(html)\n  .then((errors) =\u003e {           \n      errors.forEach(error =\u003e {         \n         console.log(`${error.message} [ln: ${error.line} col: ${error.column}]`);\n             if(error.detail) console.log(`  * ${error.detail}`);\n      });\n  });\n```\n\ncan be configured by passing a config object\n\n```js\nconst Config = require('aurelia-template-lint').Config\n\nvar config = new Config();\n\nconfig.obsoleteTagOpts.push({tag:'my-old-tag', msg:'is really old'});\n\nvar linter = new AureliaLinter(config);\n```\n\n## Config\n\nConfig is an object type of the form and default:\n\n```ts\nexport class Config {\n\n    useRuleAttributeValue = true;         // error on bad attribute value\n    useRuleObsoleteAttribute = true;      // error on use of obsolete attributes\n    useRuleObsoleteTag = true;            // error on use of obsolete tags\n    useRuleConflictingAttribute = true    // error on use of conflicting attributes\n    useRuleSelfClose = true;              // error on self-closed tags\n    useRuleStructure = true;              // error on mismatched tags (unclosed)\n    useRuleValidChildren = true;          // error on use of invalid child elements \n    useRuleRequiredAttributes = true;      // error on missing attrs on tags\n \n    useRuleAureliaRequire = true;         // error on bad require tag usage (aurelia-flavor)\n    useRuleAureliaSlot = true;            // error on bad slot usage (aurelia-flavor)\n    useRuleAureliaTemplate = true;        // error on bad template usage (aurelia-flavor)\n    useRuleAureliaBindingAccess = false;  // error on bad view-model binding, when type is known (static type checking)\n    useRuleAureliaBindingSyntax = true;   // error on bad binding syntax (as reported by aurelia) \n\n    /**\n     * Attribute Value Rules\n     * attr: attributes that matches this reg-ex are checked\n     * tag: applies the rule only on a specific element-tag, other-wise applies to all\n     * msg: the error to report if the rule fails\n     * is: the attribute value must match (entirely) the reg-ex.\n     * not: the attribute value must not match (partially) the reg-ex. \n     */\n    attributeValueOpts: Array\u003c{ attr: RegExp, is?: RegExp, not?: RegExp, msg?: string, tag?: string }\u003e = [\n        {\n            attr: /^style$/,\n            not: /\\${(.?)+}/,\n            msg: \"interpolation not allowed in style attribute\"\n        },\n        {\n            attr: /^bindable$/,\n            not: /[a-z][A-Z]/,\n            msg: \"camelCase bindable is converted to camel-case\",\n            tag: \"template\"\n        },\n        {\n            tag: \"button\",\n            attr: /^type$/,\n            is: /^button$|^submit$|^reset$|^menu$/,\n            msg: \"button type invalid\"\n        }\n    ]\n\n   /**\n    * Required Attributes Rules\n    * tag: applies the rule to matching element tags\n    * attr: must have an attribute that matches the reg-ex.\n    * msg: the error to report if the rule fails\n    */\n    requiredAttribute: Array\u003c{ tag: RegExp, attr: RegExp, msg: string }\u003e = [{\n      tag: /^button$/,\n      attr: /^type$/,\n      msg: \"buttons without a type have irregular behavour\"\n    }];\n\n    /**\n     * Obsolete Tag Rules     \n     * tag: the obsolete element\n     * msg: the error to report if the element is found\n     */\n    obsoleteTagOpts: Array\u003c{ tag: string, msg?: string }\u003e = [\n        {\n            tag: 'content',\n            msg: 'use slot instead'\n        }\n    ];\n\n    /**\n    * Obsolete Attribute Rules\n    * attr: the attribute name that is obsolete   \n    * tag: [optional] obsolete only when applied to a specfic element tag\n    * msg: the error to report if the attribute is found\n    */\n    obsoleteAttributeOpts: Array\u003c{ attr: string, tag?: string, msg?: string }\u003e = [\n    ];\n\n    /**\n    * Conflicting Attribute Rules\n    * attrs: the attributes that cannot be used on the same element\n    * msg: the error to report if the rule fails\n    */\n    conflictingAttributeOpts: Array\u003c{ attrs: string[], msg?: string }\u003e = [\n        {\n            attrs: [\"repeat.for\", \"if.bind\", \"with.bind\"],\n            msg: \"template controllers shouldn't be placed on the same element\"\n        }\n    ];\n\n    /**\n    * ID Attribute Rule\n    *\n    */\n    idAttributeOpts = {\n      allowEmptyId: false,\n      allowDuplicateId: false,\n      allowIllegalChars: false,\n      ignoreAny: /\\$\\{[\\s\\S]+\\}/,\n    };\n\n    /**\n    * Valid Child Rule\n    */\n    validChildOpts = [ \n      // { element: \"name\", allow: [], /*OR*/ exclude: []},\n      { element: \"tr\", allow: [\"td\", \"th\"] },\n      { element: \"ul\", allow: [\"li\"] },\n      { element: \"ol\", allow: [\"li\"] },\n      { element: \"dl\", allow: [\"dt\", \"dd\"] },\n      { element: \"select\", allow: [\"option\", \"optgroup\"] },\n    ];\n\n    /**\n    * Parser Options\n    * voids: list of elements that do not have a close tag.   \n    * scopes: list of element that change the language scope.  \n    */\n    parserOpts = {\n        voids: ['area', 'base', 'br', 'col', 'embed', 'hr',\n            'img', 'input', 'keygen', 'link', 'meta',\n            'param', 'source', 'track', 'wbr'],\n\n        scopes: ['html', 'body', 'template', 'svg', 'math']\n    }\n\n    /**\n    * Aurelia Binding Access Options\n    * localProvidors: list of attributes that generate local variables\n    * debugReportExceptions: when true, any caught exceptions are reported as rule issues. \n    * restrictedAccess: access to type members with these modifiers will report an issue;\n    */\n    aureliaBindingAccessOpts = {\n        localProvidors: [\n            \"repeat.for\", \"if.bind\", \"with.bind\"\n        ],\n        localOverride: new Map([\n            [\"my-tag\", [{ name: \"stubornLocal\", typeValue: {} }]]\n        ]),\n        restrictedAccess: [\"private\", \"protected\"],\n        reportUnresolvedViewModel: false\n    }\n\n    \n    /**\n    * Aurelia Slot Options\n    * controllers: attributes that create template controllers\n    */\n    aureliaSlotOpts = {\n        controllers: [\n            \"repeat.for\", \"if.bind\", \"with.bind\"\n        ]\n    }\n    \n    /**\n    * Aurelia Template Options\n    * containers: html container elements (used to ensure no repeat-for usage)\n    */\n    aureliaTemplateOpt = {\n        containers: ['table', 'select']\n    }\n\n    /**\n    * Reflection Options\n    * sourceFileGlob: glob pattern used to load source files (ts)\n    * typingsFileGlob: glob pattern used to load typescript definition files. \n    */\n    reflectionOpts: {\n      sourceFileGlob: string | string[],\n      typingsFileGlob: string | string[]\n    } = {\n      sourceFileGlob: \"source/**/*.ts\",\n      typingsFileGlob: \"typings/**/*.d.ts\",\n    };\n\n    /**\n     * report exceptions as issues, where applicable \n     */\n    debug = false;\n    \n    /**\n     * Append the linter rule-set with these rules\n     */\n    customRules: Rule[] = [];\n}\n```\n\n## Example\nusing the default config (plus type checking - *see below*), the example:\n\n\n***foo.html***\n```html\n01:\u003ctemplate\u003e\n02:  \u003crequire/\u003e\n03:  \u003cdiv repeat.for=\"item of\"\u003e\u003c/div\u003e\n04:  \u003ccontent\u003e\u003c/content\u003e\n05:  \u003cslot\u003e\u003c/slot\u003e\u003cslot\u003e\u003c/slot\u003e\n06:  \u003ctable\u003e\n07:    \u003ctemplate\u003e\u003c/template\u003e\n08:  \u003c/table\u003e\n09:  \u003cdiv style=\"width: ${width}px; height: ${height}px;\"\u003e\u003c/div\u003e\n10:  \u003cdiv repeat.for=\"item of items\" with.bind=\"items\"\u003e\u003c/div\u003e\n11:  \u003ctemplate repeat.for=\"item of items\"\u003e\n12:    ${item.ino} \n13:    ${item.role.isAdmn} \n14:    ${item.update().sizeee}\n15:  \u003c/template\u003e\n16:  \u003ctemplate with.bind=\"person\"\u003e\n17:    ${address.postcdo}\n18:  \u003c/template\u003e\n19:  \u003ctable\u003e\n20:    \u003ctr repeat.for=\"item of items\"\u003e\n21:      \u003ctd\u003e${item.nme}\u003c/td\u003e\n22:    \u003c/tr\u003e\n23:  \u003c/table\u003e\n24:  \u003cdiv value.bind=\"car.modl\"\u003e\u003c/div\u003e\n25:\u003c/etemps\u003e\n```\n***foo.ts***\n```ts\nimport {Person} from './my-types/person';\nimport {Item} from './my-types/item';\nimport {Car} from 'my-lib';\n\nexport class FooViewModel {\n  person: Person;\n  items: Item[];\n  car: Car;\n  width:number;\n  height:number;\n}\n```\n\nwill result in the following errors:\n\n```\nsuspected unclosed element detected [ln: 1 col: 1]\nself-closing element [ln: 2 col: 3]\nrequire tag is missing a 'from' attribute [ln: 2 col: 3]\nIncorrect syntax for \"for\" [ln: 3 col: 8]\n  * The form is: \"$local of $items\" or \"[$key, $value] of $items\",\n\u003ccontent\u003e is obsolete [ln: 4 col: 3]\n  * use slot instead\nmore than one default slot detected [ln: 5 col: 16]\ntemplate as child of \u003ctable\u003e not allowed [ln: 7 col: 5]\ninterpolation not allowed in style attribute [ln: 9 col: 3]\nconflicting attributes: [repeat.for, with.bind] [ln: 10 col: 3]\n  * template controllers shouldn't be placed on the same element\ncannot find 'ino' in type 'Item' [ln: 13 col: 5]\ncannot find 'isAdmn' in type 'Role' [ln: 15 col: 5]\ncannot find 'sizeee' in type 'Data' [ln: 17 col: 5]\ncannot find 'postcdo' in type 'Address' [ln: 18 col: 5]\ncannot find 'nme' in type 'Item' [ln: 21 col: 11]\ncannot find 'modl' in type 'Car' [ln: 24 col: 8]\nmismatched close tag [ln: 25 col: 1]\n```\n\nThe full example is available in the repository; including the custom typings. \n\n## Rules\nRules used by default:\n\n* **SelfClose**\n  * *ensure non-void elements do not self-close*\n* **Parser**\n  * *returns detected unclosed/ill-matched elements errors captured during parsing*\n* **ObsoleteTag**\n  * *identify obsolete tag usage*\n* **ObsoleteAttributes**\n  * *identify obsolete attribute usage*\n* **AttributeValue**\n  * *ensure attributes exactly match an expected pattern*\n  * *ensure attributes don't contain any matches of an pattern*\n  * *ensure attribute is used for a tag*\n* **Slot**\n  * *don't allow two, or more, slots to have the same name;*\n  * *don't allow more than one default slot;*  \n* **Require**\n  * *ensure require elments have a 'from' attribute*\n* **ConflictingAttributes**\n  * *ensure element doesn't have attribute combination marked as conflicting.* \n  * *i.e. template controller attributes (`if.bind` and `repeat.for` on the same element)*\n* **Template**\n  * *ensure root is a template element, unless its \u003chtml\u003e*\n  * *no more than one template element present*\n* **Binding Syntax**\n  * *ensure binding syntax is correct*\n* **Binding Access**\n  * *ensure binding correlates with fields of known types (static type checking)*\n\nI'm more than happy to add or improve rules;\nso please feel free to [create an issue](https://github.com/MeirionHughes/aurelia-template-lint/labels/rule),\nor even a pull request.\n\n\n## Static Type Checking\nIn order to use static type checking you must opt-in by setting `useRuleAureliaBindingAccess = true`. \n\nyour template html and source must have a path that defined as being in the same directory, i.e: *\"source/foo.html\"* and *\"source/foo.ts\"*. \nYou pass the path of the html file to the lint function. See Below. \n\nIt is posible to change the glob configuration to include javascript files instead of typescript; but this can only \ncheck first-depth access. \n\n```js\nvar config = new Config();\n\nconfig.useRuleAureliaBindingAccess = true;\nconfig.reflectionOpts.sourceFileGlob = \"example/**/*.ts\"; //or \"example/**/*.js\"\n\nvar linter = new AureliaLinter(config);\n\nvar htmlpath = \"./example/foo.html\";\nvar html = fs.readFileSync(htmlpath, 'utf8');\n\nlinter.lint(html, htmlpath)\n  .then((results) =\u003e {\n    results.forEach(error =\u003e {\n      console.log(`${error.message} [ln: ${error.line} col: ${error.column}]`);\n      if (error.detail) console.log(`  * ${error.detail}`);\n    });\n  });\n```\n\nplease [report any false-negatives, code exceptions or issues](https://github.com/MeirionHughes/aurelia-template-lint/issues/35) with an example of what (HTML/TS) causes the problem. \n\nalso note it will probably be far easier to use this via [gulp plugin](https://github.com/MeirionHughes/gulp-aurelia-template-lint), where you'll only need to pass the config object\n\n## Background\nThis project was the result of wondering why aurelia applications had missing content when you used self-closing tags.\nIn the end it turns out that if your template html is ill-formed, the browser's parser will not complain and you will simply have missing content\nand/or an ill formed DOM element tree.\n\nSee:\n* [StackOverflow: aurelia-self-closing-require-element-does-not-work](http://stackoverflow.com/questions/37300986/aurelia-self-closing-require-element-does-not-work)\n* [StackOverflow: aurelia-sanity-check-template-html](http://stackoverflow.com/questions/37322985/aurelia-sanity-check-template-html)\n\n\n## Compiling\nClone the repository. \nIn the project root run\n```shell\nnpm install\nnpm test\n```\n\ntest with: \n```shell\ngulp compile:typescript \u0026\u0026 node example.js\n```\n\n## Visual Studio Code\n\nOnce installed, you can use make use of Visual Studio Code launcher (`ctrl + f5`). Also allows you to place breakpoints on ts spec files (currently only for those files in `outDir` path in `launch.json` see: https://github.com/Microsoft/vscode/issues/6915) \n  \n\n## Contributors\nSpecial thanks to:\n\n* **atsu85** - https://github.com/atsu85\n* **Jan Žák** - https://github.com/zakjan\n\nIf you would like to contribute code or failing tests (for rules you'd like) you are most welcome to do so. \nPlease feel free to PR or raise an issue. :)  \n\n## Icon\n\nIcon courtesy of [The Noun Project](https://thenounproject.com/)\n\n[npm-url]: https://npmjs.org/package/aurelia-template-lint\n[npm-image]: http://img.shields.io/npm/v/aurelia-template-lint.svg\n[npm-downloads]: http://img.shields.io/npm/dm/aurelia-template-lint.svg\n[travis-url]: https://travis-ci.org/MeirionHughes/aurelia-template-lint\n[travis-image]: https://img.shields.io/travis/MeirionHughes/aurelia-template-lint/master.svg\n[breaks-image]: https://img.shields.io/badge/breaks--on-minor-yellow.svg\n[stability-image]: https://img.shields.io/badge/stability-2%20%3A%20unstable-red.svg\n\n[gitter-image]: https://img.shields.io/gitter/room/MeirionHughes/aurelia-template-lint.svg\n[gitter-url]:https://gitter.im/MeirionHughes/aurelia-template-lint\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faurelia%2Ftemplate-lint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faurelia%2Ftemplate-lint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faurelia%2Ftemplate-lint/lists"}