hint
Version:
The linting tool for the web
205 lines (154 loc) • 6.11 kB
Markdown
# 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][new 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`][browserconfiguration] 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`.
```js
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:
```js
const script =
`(function() {
return true;
}())`;
context.evaluate(script);
```
```js
const script =
`(function() {
return Promise.resolve(true);
}())`;
context.evaluate(script);
```
The following does not:
```js
const script = `return true;`;
context.evaluate(script);
```
```js
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.
```ts
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:
```ts
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][ssllabs code].
## Validate JavaScript
To create a hint that understands JavaScript you will need to use the
event `parse::javascript` emitted by the [`javascript parser`][parsers].
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:
```ts
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:
```ts
hintRunner.testHint(hintPath, tests, { parsers: ['javascript'] });
```
<!-- Link labels: -->
[browserconfiguration]: ../../user-guide/configuring-webhint/browser-context.md
[new issue]: https://github.com/webhintio/hint/issues/new
[parsers]: ../../user-guide/concepts/parsers.md
[ssllabs code]: https://github.com/webhintio/hint/blob/main/packages/hint-ssllabs/src/hint.ts