This site uses cookies for analytics. By continuing to browse this site, you agree to this use.

Implement common hint scenarios

Implement common hint scenarios

This page documents the most common scenarios encoutered when developing a hint for webhint. If there’s something that you want to do that is not documented here, please open an issue so we can help you.

Change feedback based on browser support

Users can tell webhint what browsers are important for them via a browserslist property added in .hintrc or in the package.json file. Hints can access this list (and modify their feedback) via the property content.targetedBrowsers.

You can have access to the list, and thus modify the feedback of your hint, via the property context.targetedBrowsers.

const validate = (fetchEnd) => {
    if (!context.targetedBrowsers.includes('Edge 14')) {
        return;
    }

    // Your validations
};

Reporting an error if not run

Sometimes what a hint checks is mandatory, and if it does not have the chance to test it, it should fail. These are the types of hints that enforce certain things to be used in a certain way, and if included, in order for the hint to pass, the expectation should be that the thing the hint checks for should exist and be valid/used correctly.

For example, there could be a hint that checks that a particular JavaScript file is loaded (an analytics library). If it isn’t, it should fail.

The recommended way to implement a hint like this is to subscribe to the event scan::end. If the hint receives that event and has not run any validation it should report an issue.

Evaluate JavaScript in the page context

Sometimes a hint needs to evaluate some JavaScript in the context of the page. To do that you need to use context.evaluate. This method will always return a Promise even if your code does not return one.

One important thing is that your code needs to be wrapped in an immediate invoked function expression (IIFE).

The following scripts will work:

const script =
`(function() {
    return true;
}())`;

context.evaluate(script);
const script =
`(function() {
    return Promise.resolve(true);
}())`;

context.evaluate(script);

The following does not:

const script = `return true;`;

context.evaluate(script);
const script = `return Promise.resolve(true);`;

context.evaluate(script);

Ignore connectors

If your hint does not work properly with certain connectors you can use the property ignoreConnectors so it is not run when they are used.

export default class MyHint implements IHint {
    public static readonly meta: HintMetadata = {
        id: 'my-hint',
        ignoredConnectors: ['jsdom']
    }

    public constructor(context: HintContext) {
        // Your code here
    }
}

Interact with other services

You can develop a hint that integrates with other services. webhint integrates with a few such as ssllabs.

Because these online tools usually take a few seconds to return the results the guidance is to start the analysis as soon as possible and then collect the results as late as possible. This means you will have to listen to scan::start and scan::end events respectively. The create method of your hint should be similar to the following:

export default class MyHint implements IHint {
    public static readonly meta: HintMetadata = { id: 'my-hint' }

    public constructor(context: HintContext) {
        /** The promise that represents the connection to the online service. */
        let promise: Promise<any>;

        const start = (data: ScanStartEvent) => {
            // Initialize promise to service here but do not return it.
            promise = new MyService();
        };

        const end = (data: ScanEndEvent): Promise<any> => {
            return promise
                .then((results) => {
                    // Report any results via `context.report` here.
                })
                .catch((e) => {
                    // Always good to handle errors.
                });
        };

        context.on('scan::start', start);
        context.on('scan::end', end);
    }
}

In case you need a more complete example, please look at the ssllabs.ts source code.

Validate JavaScript

To create a hint that understands JavaScript you will need to use the event parse::javascript emitted by the javascript parser. This event is of type IScriptParse which has the following information:

  • resource: the parsed resource. If the JavaScript is in a <script> tag and not a file, the value will be Internal javascript.
  • sourceCode: a eslint SourceCode object.

Here is an example hint that use the parser:

import * as eslint from 'eslint';

export default class ScriptSemiColonHint implements IHint {
    public static readonly meta: HintMetadata = {
        docs: {
            category: Category.compatibility,
            description: `Check if your scripts use semicolon`
        },
        id: 'script-semicolon',
        recommended: false,
        schema: [],
        worksWithLocalFiles: true
    }

    public constructor(context: HintContext) {
        let validPromise;
        const errorsOnly = context.hintOptions && context.hintOptions['errors-only'] || false;
        let html;

        const onParseJavascript = (scriptParse: ScriptParse) => {
            const results = linter.verify(scriptParse.sourceCode, { hints: { semi: 2 } });

            for (const result of results) {
                context.report(scriptParse.resource, null, result.message);
            }
        };

        context.on('parse::javascript', onParseJavascript);
    }
}

And when writing tests, you need to specify the parsers that you need:

hintRunner.testHint(hintPath, tests, { parsers: ['javascript'] });