Skip to content

docs: Add Custom Rules info #1674

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 22, 2025
Merged

docs: Add Custom Rules info #1674

merged 3 commits into from
Jun 22, 2025

Conversation

coliff
Copy link
Member

@coliff coliff commented Jun 22, 2025

This pull request introduces comprehensive support for custom HTMLHint rules, including examples, documentation, and CLI enhancements. The changes aim to make it easier for users to create, configure, and use custom rules to extend HTMLHint's functionality.

Custom Rule Examples and Implementation:

  • examples/custom-rules/example-rule.js: Added an example custom rule that checks for accessibility attributes on images and validates class names against a configurable pattern. The rule demonstrates integration with the HTML parser and reporter.
  • examples/custom-rules/README.md: Documented the example custom rule, including its purpose, usage, configuration, and testing instructions.

Documentation Updates:

This comment was marked as resolved.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds comprehensive documentation for creating and using custom rules in HTMLHint, including a new documentation page, examples, and updates to CLI and options documentation. The documentation is well-structured and detailed.

My review focuses on improving the correctness and robustness of the provided code examples. The key suggestions are:

  • Using arrow functions in event listeners to ensure the correct this context.
  • Handling rule options more safely, especially for falsy values like 0.
  • Improving performance and robustness in the example rule by caching RegExp objects and handling potential errors.

These changes will ensure the examples follow best practices and are more reliable for users to copy and adapt.

Comment on lines 10 to 45
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
)
}
}
}
})
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This example rule can be improved to demonstrate best practices for writing custom rules:

  1. this context: The this context within the addListener callback is not guaranteed to be the rule instance when using a function expression. It's safer to use an arrow function (event) => { ... } to lexically bind this to the rule instance from the init function's scope.
  2. Performance: The RegExp for classPattern is created on every tagstart event. For better performance, it should be created only once during the init phase.
  3. Robustness: The classPattern option comes from user configuration and could be an invalid regular expression. This would cause new RegExp() to throw an error and crash the linting process. It's good practice to wrap this in a try...catch block.

Here's a suggested revision that addresses these points.

    init: function (parser, reporter, options) {
      let classPattern;
      if (options && options.classPattern) {
        try {
          classPattern = new RegExp(options.classPattern);
        } catch (e) {
          // Invalid RegExp pattern from user config, so we can't validate class names.
        }
      }

      // Listen for start tags
      parser.addListener('tagstart', (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 && 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
              )
            }
          }
        }
      })
    },

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The line const maxAttrs = options || 5; is not robust for handling options. If a user configures "max-attributes": 0 in their .htmlhintrc, options will be 0, and 0 || 5 will evaluate to 5. This is likely not the user's intent.

A safer way to provide a default value is to check the type of options or check specifically for undefined.

      const maxAttrs = typeof options === 'number' ? options : 5; // Default to 5 if no options provided

@coliff coliff merged commit 9ca2b99 into main Jun 22, 2025
15 checks passed
@coliff coliff deleted the dev/coliff/add-custom-rules-docs branch June 22, 2025 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant