id | title |
---|---|
linters |
Linters |
TypeSpec library can probide a $onValidate
hook which can be used to validate the typespec program is valid in the eye of your library.
A linter on the other hand might be a validation that is more optional, the program is correct but there could be some improvements. For example requiring documentation on every type. This is not something that is needed to represent the typespec program but without it the end user experience might suffer.
There is no built-in concept of linter into TypeSpec, there is however a library @typespec/lint
that lets a library define its linting rules and hooks on to the onValidate
.
npm install @typespec/lint
import { createRule } from "@typespec/lint";
import { reportDiagnostic } from "../lib.js";
export const modelDocRule = createRule({
name: "no-model-doc",
create({ program }) {
return {
model: (model) => {
if (!getDoc(program, model)) {
reportDiagnostic(program, {
// Note 1
code: "no-model-doc",
target: model,
});
}
},
};
},
});
Note 1: code
refers to they key of a diagnostic defined when you createTypeSpecLibrary
See
$lib
refer to the value of createTypeSpecLibrary
See
import { $lib } from "../lib.js";
import { modelDocRule } from "./rules/model-doc.js";
// Get the instance of the linter for your library
const linter = getLinter($lib);
linter.registerRule(modelDocRule);
Or multiple rules at once
linter.registerRules([modelDocRule, interfaceDocRule]);
When registering a rule, its name will be prefixed by the library named defined in $lib
.
Rules are by default just registered but not enabled. This allows a library to provide a set of linting rules for other libraries to use or a user to enable.
// Note: The rule ID here needs to be the fully qualified rule name prefixed with the
// `<libraryname>/` prefix and it cannot be a rule provided by another library.
linter.enableRule("my-library/no-model-doc");
Alternatively rules can be automatically enabled when registered.
// With single registration
linter.registerRule(modelDocRule, { autoEnable: true });
// With multi registration
linter.registerRules([modelDocRule, interfaceDocRule], { autoEnable: true });
The lint library still depends on $onValidate
to run. For that each library providing a linter should call linter.lintOnValidate(program);
to ensure that the linter will be run.
export function $onValidate(program: Program) {
// Optional if you want to automatically enable your rules.
linter.autoEnableRules();
// Alternatively enable rules explicitly.
// Note: The rule IDs here needs to be the fully qualified rule names with the
// `<libraryname>/` prefix and they cannot be rules provided by other libraries.
linter.enableRules(["<library-name>/<rule-name-1>", "<library-name>/<rule-name-2>]);
linter.lintOnValidate(program);
}
This will not run the linter right here, it will just add a new callback to the onValidate list giving time for all linters to register their rules.
To test linter rule an rule tester is provided letting you test a specific rule without enabling the others.
First you'll want to create an instance of the rule tester using createRuleTester
passing it the rule that is being tested.
You can then provide different test checking the rule pass or fails.
import { createRuleTester } from "@typespec/lint/testing";
import { noFooModelRule } from "./no-foo-model.js";
let ruleTester: RuleTester;
beforeEach(() => {
const runner = createTestRunner();
ruleTester = createRuleTester(runner, noFooModelRule);
});
it("emit diagnostics when using model named foo", () => {
ruleTester.expect(`model Foo {}`).toEmitDiagnostic({
code: "my-library/no-foo-model",
message: "Cannot name a model with 'Foo'",
});
});
it("should be valid to use other names", () => {
ruleTester.expect(`model Bar {}`).toBeValid();
});