From 57284f6b8d8f29237827dad3adda8395c3ef2e06 Mon Sep 17 00:00:00 2001 From: coliff Date: Sun, 22 Jun 2025 17:24:57 +0900 Subject: [PATCH 1/3] docs: Add Custom Rules info --- examples/custom-rules/README.md | 65 +++++ examples/custom-rules/example-rule.js | 47 ++++ website/src/content/docs/usage/cli.md | 10 +- .../src/content/docs/usage/custom-rules.mdx | 251 ++++++++++++++++++ website/src/content/docs/usage/options.md | 76 ++++++ 5 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 examples/custom-rules/README.md create mode 100644 examples/custom-rules/example-rule.js create mode 100644 website/src/content/docs/usage/custom-rules.mdx diff --git a/examples/custom-rules/README.md b/examples/custom-rules/README.md new file mode 100644 index 000000000..8df2e01a4 --- /dev/null +++ b/examples/custom-rules/README.md @@ -0,0 +1,65 @@ +# Custom Rules Examples + +This directory contains example custom rules for HTMLHint to demonstrate how to create and use custom rules. + +## Example Rule + +The `example-rule.js` file demonstrates a custom rule that: + +1. Checks if images have either a `title` or `alt` attribute for accessibility +2. Validates class names against a configurable pattern + +## Usage + +### Loading the Example Rule + +```shell +# Load the example rule +npx htmlhint --rulesdir ./examples/custom-rules/example-rule.js test.html +``` + +### Configuration + +You can configure the example rule in your `.htmlhintrc` file: + +```json +{ + "example-rule": { + "classPattern": "^[a-z][a-z0-9-]*$" + } +} +``` + +Or via command line: + +```shell +npx htmlhint --rulesdir ./examples/custom-rules/ --rules "example-rule:{classPattern:'^[a-z][a-z0-9-]*$'}" test.html +``` + +### Testing + +Create a test HTML file to see the rule in action: + +```html + + + + Test + + + + + + +
Content
+ + + Description +
Content
+ + +``` + +## Creating Your Own Rules + +See the [Custom Rules documentation](../../website/src/content/docs/usage/custom-rules.mdx) for detailed information on creating custom rules. diff --git a/examples/custom-rules/example-rule.js b/examples/custom-rules/example-rule.js new file mode 100644 index 000000000..02ce1b3a1 --- /dev/null +++ b/examples/custom-rules/example-rule.js @@ -0,0 +1,47 @@ +/** + * Example custom rule for HTMLHint + * This rule demonstrates how to create a custom rule that checks for specific patterns + */ + +module.exports = function (HTMLHint) { + HTMLHint.addRule({ + id: 'example-rule', + description: 'Example custom rule that demonstrates custom rule creation', + init: function (parser, reporter, options) { + // Listen for start tags + parser.addListener('tagstart', function (event) { + const tagName = event.tagName.toLowerCase() + const mapAttrs = parser.getMapAttrs(event.attrs) + + // Example: Check if elements have a title attribute when they should + if (tagName === 'img' && !mapAttrs.title && !mapAttrs.alt) { + reporter.warn( + 'Images should have either a title or alt attribute for accessibility', + event.line, + event.col, + this, + event.raw + ) + } + + // Example: Check for specific class naming convention + if (mapAttrs.class && options && options.classPattern) { + const classPattern = new RegExp(options.classPattern) + const classes = mapAttrs.class.split(/\s+/) + + for (const className of classes) { + if (!classPattern.test(className)) { + reporter.warn( + `Class "${className}" does not match the required pattern: ${options.classPattern}`, + event.line, + event.col, + this, + event.raw + ) + } + } + } + }) + }, + }) +} diff --git a/website/src/content/docs/usage/cli.md b/website/src/content/docs/usage/cli.md index 9a099d2f1..dda5a921e 100644 --- a/website/src/content/docs/usage/cli.md +++ b/website/src/content/docs/usage/cli.md @@ -40,7 +40,15 @@ Set all of the rules available ### `--rulesdir, -R` -Load custom rules from file or folder +Load custom rules from file or folder. See the [custom rules documentation](/usage/custom-rules/) for detailed information on creating and using custom rules. + +```shell +# Load a single custom rule file +npx htmlhint --rulesdir ./my-custom-rule.js index.html + +# Load all custom rules from a directory +npx htmlhint --rulesdir ./custom-rules/ index.html +``` ### `--version, -V` diff --git a/website/src/content/docs/usage/custom-rules.mdx b/website/src/content/docs/usage/custom-rules.mdx new file mode 100644 index 000000000..8a0b236d0 --- /dev/null +++ b/website/src/content/docs/usage/custom-rules.mdx @@ -0,0 +1,251 @@ +--- +id: custom-rules +title: Custom Rules +description: Learn how to create and use custom rules to extend HTMLHint's functionality for your specific needs. +sidebar: + order: 4 +--- + +# Custom Rules + +HTMLHint allows you to create custom rules to extend its functionality for your specific needs. Custom rules can be loaded using the `--rulesdir` option and follow the same pattern as built-in rules. + +## Creating Custom Rules + +Custom rules are JavaScript modules that export a function. The function receives the `HTMLHint` instance as a parameter and should register the custom rule using `HTMLHint.addRule()`. + +### Basic Custom Rule Structure + +```javascript +// my-custom-rule.js +module.exports = function(HTMLHint) { + HTMLHint.addRule({ + id: 'my-custom-rule', + description: 'This is my custom rule description', + init: function(parser, reporter, options) { + // Rule implementation goes here + } + }); +}; +``` + +### Rule Object Properties + +Each custom rule should have the following properties: + +- **`id`** (string): Unique identifier for the rule. This is used in configuration files and command line options. +- **`description`** (string): Human-readable description of what the rule does. This appears in the `--list` output. +- **`init`** (function): Function that initializes the rule with the parser and reporter. + +### The `init` Function + +The `init` function is where your rule logic goes. It receives three parameters: + +- **`parser`**: The HTML parser instance that provides events as it parses the HTML +- **`reporter`**: The reporter instance for generating warnings, errors, or info messages +- **`options`**: Rule-specific options from the configuration + +## Example Custom Rules + +### Example 1: Simple Tag Check + +This rule warns when a specific tag is used: + +```javascript +// no-div-tags.js +module.exports = function(HTMLHint) { + HTMLHint.addRule({ + id: 'no-div-tags', + description: 'Div tags are not allowed', + init: function(parser, reporter, options) { + parser.addListener('tagstart', function(event) { + const tagName = event.tagName.toLowerCase(); + + if (tagName === 'div') { + reporter.warn( + 'Div tags are not allowed. Use semantic HTML elements instead.', + event.line, + event.col, + this, + event.raw + ); + } + }); + } + }); +}; +``` + +### Example 2: Attribute Validation + +This rule checks for specific attribute patterns: + +```javascript +// data-attributes-required.js +module.exports = function(HTMLHint) { + HTMLHint.addRule({ + id: 'data-attributes-required', + description: 'Elements with class "component" must have a data-component attribute', + init: function(parser, reporter, options) { + parser.addListener('tagstart', function(event) { + const tagName = event.tagName.toLowerCase(); + const mapAttrs = parser.getMapAttrs(event.attrs); + + // Check if element has class "component" + if (mapAttrs.class && mapAttrs.class.includes('component')) { + // Check if it has data-component attribute + if (!mapAttrs['data-component']) { + reporter.warn( + 'Elements with class "component" must have a data-component attribute', + event.line, + event.col, + this, + event.raw + ); + } + } + }); + } + }); +}; +``` + +### Example 3: Complex Validation with Options + +This rule accepts configuration options: + +```javascript +// max-attributes.js +module.exports = function(HTMLHint) { + HTMLHint.addRule({ + id: 'max-attributes', + description: 'Elements should not have more than the specified number of attributes', + init: function(parser, reporter, options) { + const maxAttrs = options || 5; // Default to 5 if no options provided + + parser.addListener('tagstart', function(event) { + const attrCount = event.attrs.length; + + if (attrCount > maxAttrs) { + reporter.warn( + `Element has ${attrCount} attributes, but maximum allowed is ${maxAttrs}`, + event.line, + event.col, + this, + event.raw + ); + } + }); + } + }); +}; +``` + +## Using Custom Rules + +### Loading Custom Rules + +Use the `--rulesdir` option to load custom rules: + +```shell +# Load a single custom rule file +npx htmlhint --rulesdir ./my-custom-rule.js index.html + +# Load all custom rules from a directory +npx htmlhint --rulesdir ./custom-rules/ index.html +``` + +### Enabling Custom Rules + +After loading custom rules, you can enable them in several ways: + +#### In Configuration File + +```json +{ + "no-div-tags": true, + "data-attributes-required": true, + "max-attributes": 3 +} +``` + +#### Via Command Line + +```shell +npx htmlhint --rulesdir ./custom-rules/ --rules no-div-tags,data-attributes-required,max-attributes:3 index.html +``` + +#### Inline in HTML + +```html + + + +
This will trigger warnings
+ + +``` + +## Parser Events + +The HTML parser provides several events you can listen to: + +- **`tagstart`**: Fired when a start tag is encountered +- **`tagend`**: Fired when an end tag is encountered +- **`text`**: Fired when text content is encountered +- **`comment`**: Fired when a comment is encountered +- **`cdata`**: Fired when CDATA is encountered +- **`doctype`**: Fired when a DOCTYPE declaration is encountered + +### Event Object Properties + +Event objects typically contain: + +- **`tagName`**: The name of the tag +- **`attrs`**: Array of attributes +- **`line`**: Line number where the event occurred +- **`col`**: Column number where the event occurred +- **`raw`**: The raw HTML string for this element + +## Reporter Methods + +The reporter provides three methods for generating messages: + +- **`reporter.warn(message, line, col, rule, raw)`**: Creates a warning message +- **`reporter.error(message, line, col, rule, raw)`**: Creates an error message +- **`reporter.info(message, line, col, rule, raw)`**: Creates an info message + +## Best Practices + +1. **Use descriptive rule IDs**: Choose clear, descriptive names for your rules +2. **Provide helpful error messages**: Make error messages actionable and informative +3. **Handle options gracefully**: Always provide sensible defaults for rule options +4. **Test your rules**: Create test cases to ensure your rules work correctly +5. **Follow existing patterns**: Look at built-in rules for examples of good practices +6. **Document your rules**: Include clear descriptions and examples + +## Directory Structure + +When organizing multiple custom rules, you can use a directory structure: + +``` +custom-rules/ +├── accessibility/ +│ ├── aria-required.js +│ └── semantic-headings.js +├── performance/ +│ ├── image-optimization.js +│ └── script-loading.js +└── style/ + ├── class-naming.js + └── attribute-order.js +``` + +HTMLHint will recursively find all `.js` files in the specified directory and load them as custom rules. + +## Troubleshooting + +- **Rule not loading**: Check that your file exports a function and calls `HTMLHint.addRule()` +- **Rule not running**: Ensure the rule is enabled in your configuration +- **Parser errors**: Verify that you're using the correct event names and object properties +- **Silent failures**: Custom rules that fail to load are silently ignored - check your JavaScript syntax diff --git a/website/src/content/docs/usage/options.md b/website/src/content/docs/usage/options.md index 3df6ab617..de1b6f00f 100644 --- a/website/src/content/docs/usage/options.md +++ b/website/src/content/docs/usage/options.md @@ -38,3 +38,79 @@ Options are: CLI flags: `--ignore, -i` A list of patterns of files or folders to ignore. For example, `--ignore="**/folder/**,**/folder_two/**"` + +## `rulesdir` + +CLI flags: `--rulesdir, -R` + +Load custom rules from a file or directory. This allows you to extend HTMLHint with your own custom rules. + +### Usage + +```shell +# Load a single custom rule file +npx htmlhint --rulesdir ./my-custom-rule.js index.html + +# Load all custom rules from a directory (recursively finds all .js files) +npx htmlhint --rulesdir ./custom-rules/ index.html +``` + +### Custom Rule Format + +Custom rules should be JavaScript modules that export a function. The function receives the `HTMLHint` instance as a parameter and should register the custom rule using `HTMLHint.addRule()`. + +#### Example Custom Rule + +```javascript +// my-custom-rule.js +module.exports = function(HTMLHint) { + HTMLHint.addRule({ + id: 'my-custom-rule', + description: 'This is my custom rule description', + init: function(parser, reporter, options) { + // Rule implementation + parser.addListener('tagstart', function(event) { + const tagName = event.tagName.toLowerCase(); + + if (tagName === 'div') { + reporter.warn( + 'Custom rule: div tags are not allowed', + event.line, + event.col, + this, + event.raw + ); + } + }); + } + }); +}; +``` + +#### Using Custom Rules + +After loading custom rules with `--rulesdir`, you can enable them in your configuration: + +```json +{ + "my-custom-rule": true +} +``` + +Or via command line: + +```shell +npx htmlhint --rulesdir ./custom-rules/ --rules my-custom-rule index.html +``` + +### Directory Loading + +When specifying a directory, HTMLHint will: + +1. Recursively search for all `.js` files in the directory +2. Load each file as a custom rule module +3. Skip any files that fail to load (errors are silently ignored) + +This makes it easy to organize multiple custom rules in a single directory structure. + +For detailed information on creating custom rules, see the [Custom Rules documentation](/usage/custom-rules/). From 0910a0aa579f741d4563dc0e1f97fd3dc7e89c66 Mon Sep 17 00:00:00 2001 From: coliff Date: Sun, 22 Jun 2025 17:32:23 +0900 Subject: [PATCH 2/3] revisions --- examples/custom-rules/example-rule.js | 4 +-- .../src/content/docs/usage/custom-rules.mdx | 36 ++++++++++++++----- website/src/content/docs/usage/options.md | 6 ++-- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/examples/custom-rules/example-rule.js b/examples/custom-rules/example-rule.js index 02ce1b3a1..a7d081578 100644 --- a/examples/custom-rules/example-rule.js +++ b/examples/custom-rules/example-rule.js @@ -8,8 +8,8 @@ module.exports = function (HTMLHint) { id: 'example-rule', description: 'Example custom rule that demonstrates custom rule creation', init: function (parser, reporter, options) { - // Listen for start tags - parser.addListener('tagstart', function (event) { + // Listen for start tags - Note: Use arrow functions for event listeners + parser.addListener('tagstart', (event) => { const tagName = event.tagName.toLowerCase() const mapAttrs = parser.getMapAttrs(event.attrs) diff --git a/website/src/content/docs/usage/custom-rules.mdx b/website/src/content/docs/usage/custom-rules.mdx index 8a0b236d0..3f92c0a46 100644 --- a/website/src/content/docs/usage/custom-rules.mdx +++ b/website/src/content/docs/usage/custom-rules.mdx @@ -45,6 +45,22 @@ The `init` function is where your rule logic goes. It receives three parameters: - **`reporter`**: The reporter instance for generating warnings, errors, or info messages - **`options`**: Rule-specific options from the configuration +### Important: Using Arrow Functions + +When adding event listeners, **always use arrow functions** instead of function expressions to ensure the correct `this` context is maintained when calling reporter methods: + +```javascript +// ✅ Correct - Arrow function preserves 'this' context +parser.addListener('tagstart', (event) => { + reporter.warn('Message', event.line, event.col, this, event.raw); +}); + +// ❌ Incorrect - Function expression loses 'this' context +parser.addListener('tagstart', function(event) { + reporter.warn('Message', event.line, event.col, this, event.raw); // 'this' is parser, not rule +}); +``` + ## Example Custom Rules ### Example 1: Simple Tag Check @@ -58,7 +74,7 @@ module.exports = function(HTMLHint) { id: 'no-div-tags', description: 'Div tags are not allowed', init: function(parser, reporter, options) { - parser.addListener('tagstart', function(event) { + parser.addListener('tagstart', (event) => { const tagName = event.tagName.toLowerCase(); if (tagName === 'div') { @@ -87,7 +103,7 @@ module.exports = function(HTMLHint) { id: 'data-attributes-required', description: 'Elements with class "component" must have a data-component attribute', init: function(parser, reporter, options) { - parser.addListener('tagstart', function(event) { + parser.addListener('tagstart', (event) => { const tagName = event.tagName.toLowerCase(); const mapAttrs = parser.getMapAttrs(event.attrs); @@ -123,7 +139,7 @@ module.exports = function(HTMLHint) { init: function(parser, reporter, options) { const maxAttrs = options || 5; // Default to 5 if no options provided - parser.addListener('tagstart', function(event) { + parser.addListener('tagstart', (event) => { const attrCount = event.attrs.length; if (attrCount > maxAttrs) { @@ -217,12 +233,13 @@ The reporter provides three methods for generating messages: ## Best Practices -1. **Use descriptive rule IDs**: Choose clear, descriptive names for your rules -2. **Provide helpful error messages**: Make error messages actionable and informative -3. **Handle options gracefully**: Always provide sensible defaults for rule options -4. **Test your rules**: Create test cases to ensure your rules work correctly -5. **Follow existing patterns**: Look at built-in rules for examples of good practices -6. **Document your rules**: Include clear descriptions and examples +1. **Use arrow functions**: Always use arrow functions for event listeners to preserve the correct `this` context +2. **Use descriptive rule IDs**: Choose clear, descriptive names for your rules +3. **Provide helpful error messages**: Make error messages actionable and informative +4. **Handle options gracefully**: Always provide sensible defaults for rule options +5. **Test your rules**: Create test cases to ensure your rules work correctly +6. **Follow existing patterns**: Look at built-in rules for examples of good practices +7. **Document your rules**: Include clear descriptions and examples ## Directory Structure @@ -249,3 +266,4 @@ HTMLHint will recursively find all `.js` files in the specified directory and lo - **Rule not running**: Ensure the rule is enabled in your configuration - **Parser errors**: Verify that you're using the correct event names and object properties - **Silent failures**: Custom rules that fail to load are silently ignored - check your JavaScript syntax +- **Incorrect rule reporting**: Make sure you're using arrow functions for event listeners to preserve the `this` context diff --git a/website/src/content/docs/usage/options.md b/website/src/content/docs/usage/options.md index de1b6f00f..f7a253df8 100644 --- a/website/src/content/docs/usage/options.md +++ b/website/src/content/docs/usage/options.md @@ -68,8 +68,8 @@ module.exports = function(HTMLHint) { id: 'my-custom-rule', description: 'This is my custom rule description', init: function(parser, reporter, options) { - // Rule implementation - parser.addListener('tagstart', function(event) { + // Rule implementation - Note: Use arrow functions for event listeners + parser.addListener('tagstart', (event) => { const tagName = event.tagName.toLowerCase(); if (tagName === 'div') { @@ -87,6 +87,8 @@ module.exports = function(HTMLHint) { }; ``` +**Important**: Always use arrow functions for event listeners to ensure the correct `this` context is maintained when calling reporter methods. + #### Using Custom Rules After loading custom rules with `--rulesdir`, you can enable them in your configuration: From 5ddfb4ecb0381745c5b52e06fe646cd7eaec559e Mon Sep 17 00:00:00 2001 From: coliff Date: Sun, 22 Jun 2025 17:36:22 +0900 Subject: [PATCH 3/3] example fixes --- examples/custom-rules/README.md | 4 ++-- website/src/content/docs/usage/custom-rules.mdx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/custom-rules/README.md b/examples/custom-rules/README.md index 8df2e01a4..e681a6841 100644 --- a/examples/custom-rules/README.md +++ b/examples/custom-rules/README.md @@ -42,7 +42,7 @@ Create a test HTML file to see the rule in action: ```html - + Test @@ -62,4 +62,4 @@ Create a test HTML file to see the rule in action: ## Creating Your Own Rules -See the [Custom Rules documentation](../../website/src/content/docs/usage/custom-rules.mdx) for detailed information on creating custom rules. +See the [Custom Rules documentation](https://htmlhint.com/docs/usage/custom-rules/) for detailed information on creating custom rules. diff --git a/website/src/content/docs/usage/custom-rules.mdx b/website/src/content/docs/usage/custom-rules.mdx index 3f92c0a46..ec4956580 100644 --- a/website/src/content/docs/usage/custom-rules.mdx +++ b/website/src/content/docs/usage/custom-rules.mdx @@ -195,7 +195,7 @@ npx htmlhint --rulesdir ./custom-rules/ --rules no-div-tags,data-attributes-requ ```html - +
This will trigger warnings