Skip to content

Latest commit

 

History

History
132 lines (94 loc) · 3.89 KB

linters.md

File metadata and controls

132 lines (94 loc) · 3.89 KB
id title
linters
Linters

Linters

Linter vs onValidate

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.

Writing a linter

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.

1. Install the @typespec/lint package

npm install @typespec/lint

2. Define the rules

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, {
            code: "no-model-doc",
            target: model,
          });
        }
      },
    };
  },
});

Register the rules

$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.registerRule([modelDocRule, interfaceDocRule]);

When registering a rule, its name will be prefixed by the library named defined in $lib.

Enable the rules

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 `<libraryname>/`
linter.enableRule("my-library/no-model-doc");

Alternatively rules can be automatically enabled when registered.

// With single registration
linter.registerRule(modelDocRule, { enable: true });

// With multi registration
linter.registerRule([modelDocRule, interfaceDocRule], { enable: true });

Register the linter hook

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) {
  linter.autoEnableMyRules(); // Optional if you want to automatically enable your rules
  linter.enableRules(["<library-name>/<rule-name>"]); // Alternatively enable rules explicitly. Must be the rule fully qualified name.
  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.

Testing a linter

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();
});