Skip to content
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

[TypeSpecValidation] Implement as ESLint Rules #31909

Draft
wants to merge 69 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
6bb00d3
WIP: Implement TSV as ESLint Rules
mikeharder Dec 17, 2024
2b9965f
Convert to TS (but still cjs)
mikeharder Dec 17, 2024
1ae2452
Convert to mjs
mikeharder Dec 17, 2024
57655ba
Add contoso test
mikeharder Dec 17, 2024
0615363
Add rule top-level-folder-lowercase
mikeharder Dec 17, 2024
6e44905
build before test
mikeharder Dec 17, 2024
ae8a8a5
Rename rule to align with eslint conventions
mikeharder Dec 17, 2024
79256c4
Rename rule
mikeharder Dec 17, 2024
85db158
Improve test
mikeharder Dec 17, 2024
4973878
Remove sample rule
mikeharder Dec 17, 2024
89006ec
Move e2e tests to separate folder
mikeharder Dec 17, 2024
0fe7173
Add comment
mikeharder Dec 17, 2024
da8c9aa
Add manual unit test
mikeharder Dec 17, 2024
abf58cc
Convert test to vitest
mikeharder Dec 17, 2024
043d24e
Remove unnecessary async
mikeharder Dec 17, 2024
6184c64
Rename rule and improve error message
mikeharder Dec 18, 2024
b03b666
Test eslint disablement comment
mikeharder Dec 18, 2024
9c09894
Add tests for disabled rule
mikeharder Dec 18, 2024
a0d0fef
Include recommended config
mikeharder Dec 18, 2024
0de1b3e
Move rules to subfolder
mikeharder Dec 18, 2024
2208b20
Cleanup ts-ignore
mikeharder Dec 18, 2024
5b3571f
Remove comment
mikeharder Dec 18, 2024
cc6d19c
WIP: Add Npm.prefix() helper method and tests
mikeharder Dec 18, 2024
659ed1e
Use memfs to test fs code
mikeharder Dec 18, 2024
a097482
Add failing tests
mikeharder Dec 19, 2024
1afea81
Implement prefix()
mikeharder Dec 19, 2024
67ddba4
Add more bulk test commands
mikeharder Dec 19, 2024
0c239f5
Add test workflow
mikeharder Dec 19, 2024
6903a12
Downgrade rimraf to v5 for node18 compat
mikeharder Dec 19, 2024
8978c86
Add code coverage
mikeharder Dec 19, 2024
83d874c
Normalize paths so tests pass on win32
mikeharder Dec 19, 2024
786140b
normalize expected value
mikeharder Dec 19, 2024
40c0796
Rename job
mikeharder Dec 19, 2024
908df37
Add comment
mikeharder Dec 19, 2024
5fe0a1f
Always return absolute path
mikeharder Dec 19, 2024
3009311
Resolve path before splitting
mikeharder Dec 19, 2024
d130711
Merge branch 'main' into eslint-plugin-tsv
mikeharder Dec 19, 2024
a92698f
Add stub for e2e vite tests
mikeharder Dec 19, 2024
3e93efa
Remove comment
mikeharder Dec 19, 2024
41814f9
Add ESLint with names
mikeharder Dec 19, 2024
5907a21
Add dummy test to prevent errors
mikeharder Dec 19, 2024
d0ae1de
Stronger config typing
mikeharder Dec 19, 2024
49d09b6
Move e2e test
mikeharder Dec 19, 2024
2563cd2
Run tests when contoso changes
mikeharder Dec 20, 2024
6a555fb
Add e2e test using real filesystem
mikeharder Dec 20, 2024
dd472f8
Remove old test
mikeharder Dec 20, 2024
d7439da
Improve test name
mikeharder Dec 20, 2024
90fab86
Fix e2e tests
mikeharder Dec 20, 2024
9f5ad9b
Exclude interface from coverage
mikeharder Dec 20, 2024
fac51d7
Exclude interfaces from coverage
mikeharder Dec 20, 2024
313f926
Add Management e2e test
mikeharder Dec 20, 2024
5cb8558
improve error validation
mikeharder Dec 20, 2024
29c9f1f
Handle invalid path errors
mikeharder Dec 20, 2024
b02de1e
Merge branch 'main' into eslint-plugin-tsv
mikeharder Dec 20, 2024
81f2cc8
Remove e2e scripts
mikeharder Dec 20, 2024
3a0425a
Merge branch 'main' into eslint-plugin-tsv
mikeharder Dec 21, 2024
c7493c1
Remove problematic vitest config
mikeharder Dec 21, 2024
adbc241
Revert "Remove problematic vitest config"
mikeharder Dec 24, 2024
61fe81b
Exclude vitest config from compilation
mikeharder Dec 24, 2024
cb8d4f5
Merge branch 'main' into eslint-plugin-tsv
mikeharder Jan 6, 2025
28a6aff
[tsconfig.json] Replace excludes with includes
mikeharder Jan 6, 2025
733db68
Add comment
mikeharder Jan 7, 2025
3b7580e
Add stub for new rule emit-autorest
mikeharder Jan 7, 2025
f5d864f
Validate tspconfig.yaml, ensure default emitter
mikeharder Jan 7, 2025
bcc439e
Merge branch 'main' into eslint-plugin-tsv
mikeharder Jan 8, 2025
83267e7
Merge branch 'main' into eslint-plugin-tsv
mikeharder Jan 21, 2025
617c0b5
bump vitest
mikeharder Jan 21, 2025
21348ee
npm update
mikeharder Jan 21, 2025
1f616a6
Merge branch 'main' into eslint-plugin-tsv
mikeharder Jan 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/eslint-plugin-tsv-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: ESLint Plugin for TypeSpec Validation - Test

Check failure on line 1 in .github/workflows/eslint-plugin-tsv-test.yaml

View workflow job for this annotation

GitHub Actions / Protected Files

File '.github/workflows/eslint-plugin-tsv-test.yaml' should only be updated by the Azure SDK team. If intentional, the PR may be merged by the Azure SDK team via bypassing the branch protections.

on:
push:
branches:
- main
- typespec-next
pull_request:
paths:
- package-lock.json
- package.json
- tsconfig.json
- .github/workflows/_reusable-eng-tools-test.yaml
- .github/workflows/eslint-plugin-tsv-test.yaml
- eng/tools/package.json
- eng/tools/tsconfig.json
- eng/tools/eslint-plugin-tsv/**
- specification/contosowidgetmanager
workflow_dispatch:

jobs:
eslint-plugin-tsv:
uses: ./.github/workflows/_reusable-eng-tools-test.yaml
with:
package: eslint-plugin-tsv
32 changes: 32 additions & 0 deletions eng/tools/eslint-plugin-tsv/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{

Check failure on line 1 in eng/tools/eslint-plugin-tsv/package.json

View workflow job for this annotation

GitHub Actions / Protected Files

File 'eng/tools/eslint-plugin-tsv/package.json' should only be updated by the Azure SDK team. If intentional, the PR may be merged by the Azure SDK team via bypassing the branch protections.
"name": "@azure-tools/eslint-plugin-tsv",
"private": true,
"type": "module",
"main": "src/index.js",
"dependencies": {
"ajv": "^8.17.1",
"yaml-eslint-parser": "^1.2.3"
},
"peerDependencies": {
"eslint": ">=9.0.0"
},
"devDependencies": {
"@types/node": "^18.19.31",
"@vitest/coverage-v8": "^3.0.2",
"eslint": "^9.17.0",
"memfs": "^4.15.0",
"rimraf": "^5.0.10",
"typescript": "~5.6.2",
"vitest": "^3.0.2"
},
"scripts": {
"build": "tsc --build",
"cbt": "npm run clean && npm run build && npm run test:ci",
"clean": "rimraf ./dist ./temp",
"test": "vitest",
"test:ci": "vitest run --coverage --reporter=verbose"
},
"engines": {
"node": ">= 18.0.0"
}
}
117 changes: 117 additions & 0 deletions eng/tools/eslint-plugin-tsv/src/config/config-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copied from https://github.com/microsoft/typespec/blob/main/packages/compiler/src/config/config-schema.ts

Check failure on line 1 in eng/tools/eslint-plugin-tsv/src/config/config-schema.ts

View workflow job for this annotation

GitHub Actions / Protected Files

File 'eng/tools/eslint-plugin-tsv/src/config/config-schema.ts' should only be updated by the Azure SDK team. If intentional, the PR may be merged by the Azure SDK team via bypassing the branch protections.

import type { JSONSchemaType } from "ajv";
import { EmitterOptions, TypeSpecRawConfig } from "./types.js";

export const emitterOptionsSchema: JSONSchemaType<EmitterOptions> = {
type: "object",
additionalProperties: true,
required: [],
properties: {
"emitter-output-dir": { type: "string", nullable: true } as any,
},
};

export const TypeSpecConfigJsonSchema: JSONSchemaType<TypeSpecRawConfig> = {
type: "object",
additionalProperties: false,
properties: {
extends: {
type: "string",
nullable: true,
},
"environment-variables": {
type: "object",
nullable: true,
required: [],
additionalProperties: {
type: "object",
properties: {
default: { type: "string" },
},
required: ["default"],
},
},
parameters: {
type: "object",
nullable: true,
required: [],
additionalProperties: {
type: "object",
properties: {
default: { type: "string" },
},
required: ["default"],
},
},

"output-dir": {
type: "string",
nullable: true,
},
"warn-as-error": {
type: "boolean",
nullable: true,
},
trace: {
oneOf: [
{ type: "string" },
{
type: "array",
items: { type: "string" },
},
],
} as any, // Issue with AJV optional property typing https://github.com/ajv-validator/ajv/issues/1664
imports: {
type: "array",
nullable: true,
items: { type: "string" },
},
emit: {
type: "array",
nullable: true,
items: { type: "string" },
},
options: {
type: "object",
nullable: true,
required: [],
additionalProperties: emitterOptionsSchema,
},
emitters: {
type: "object",
nullable: true,
deprecated: true,
required: [],
additionalProperties: {
oneOf: [{ type: "boolean" }, emitterOptionsSchema],
},
},

linter: {
type: "object",
nullable: true,
required: [],
additionalProperties: false,
properties: {
extends: {
type: "array",
nullable: true,
items: { type: "string" },
},
enable: {
type: "object",
required: [],
nullable: true,
additionalProperties: { type: "boolean" },
},
disable: {
type: "object",
required: [],
nullable: true,
additionalProperties: { type: "string" },
},
},
} as any, // ajv type system doesn't like the string templates
},
};
112 changes: 112 additions & 0 deletions eng/tools/eslint-plugin-tsv/src/config/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copied from https://github.com/microsoft/typespec/blob/main/packages/compiler/src/config/types.ts

Check failure on line 1 in eng/tools/eslint-plugin-tsv/src/config/types.ts

View workflow job for this annotation

GitHub Actions / Protected Files

File 'eng/tools/eslint-plugin-tsv/src/config/types.ts' should only be updated by the Azure SDK team. If intentional, the PR may be merged by the Azure SDK team via bypassing the branch protections.

import type { Diagnostic, RuleRef } from "@typespec/compiler";
import type { YamlScript } from "../yaml/types.js";

/**
* Represent the normalized user configuration.
*/
export interface TypeSpecConfig {
/**
* Project root.
*/
projectRoot: string;

/** Yaml file used in this configuration. */
file?: YamlScript;

/**
* Path to the config file used to create this configuration.
*/
filename?: string;

/**
* Diagnostics reported while loading the configuration
*/
diagnostics: Diagnostic[];

/**
* Path to another TypeSpec config to extend.
*/
extends?: string;

/**
* Environment variables configuration
*/
environmentVariables?: Record<string, ConfigEnvironmentVariable>;

/**
* Parameters that can be used
*/
parameters?: Record<string, ConfigParameter>;

/**
* Treat warning as error.
*/
warnAsError?: boolean;

/**
* Output directory
*/
outputDir: string;

/**
* Trace options.
*/
trace?: string[];

/**
* Additional imports.
*/
imports?: string[];

/**
* Name of emitters or path to emitters that should be used.
*/
emit?: string[];

/**
* Name of emitters or path to emitters that should be used.
*/
options?: Record<string, EmitterOptions>;

linter?: LinterConfig;
}

/**
* Represent the configuration that can be provided in a config file.
*/
export interface TypeSpecRawConfig {
extends?: string;
"environment-variables"?: Record<string, ConfigEnvironmentVariable>;
parameters?: Record<string, ConfigParameter>;

"warn-as-error"?: boolean;
"output-dir"?: string;
trace?: string | string[];
imports?: string[];

emit?: string[];
options?: Record<string, EmitterOptions>;
emitters?: Record<string, boolean | EmitterOptions>;

linter?: LinterConfig;
}

export interface ConfigEnvironmentVariable {
default: string;
}

export interface ConfigParameter {
default: string;
}

export type EmitterOptions = Record<string, unknown> & {
"emitter-output-dir"?: string;
};

export interface LinterConfig {
extends?: RuleRef[];
enable?: Record<RuleRef, boolean>;
disable?: Record<RuleRef, string>;
}
29 changes: 29 additions & 0 deletions eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import parser from "yaml-eslint-parser";

Check failure on line 1 in eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts

View workflow job for this annotation

GitHub Actions / Protected Files

File 'eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts' should only be updated by the Azure SDK team. If intentional, the PR may be merged by the Azure SDK team via bypassing the branch protections.
import { NamedESLint } from "./interfaces/named-eslint.js";
import emitAutorest from "./rules/emit-autorest.js";
import kebabCaseOrg from "./rules/kebab-case-org.js";

const plugin: NamedESLint.Plugin = {
configs: { recommended: {} },
name: "tsv",
rules: {
[kebabCaseOrg.name]: kebabCaseOrg,
[emitAutorest.name]: emitAutorest,
},
};

plugin.configs.recommended = {
plugins: {
[plugin.name]: plugin,
},
files: ["*.yaml", "**/*.yaml"],
rules: {
[`${plugin.name}/${kebabCaseOrg.name}`]: "error",
[`${plugin.name}/${emitAutorest.name}`]: "error",
},
languageOptions: {
parser: parser,
},
};

export default plugin;
17 changes: 17 additions & 0 deletions eng/tools/eslint-plugin-tsv/src/interfaces/named-eslint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ESLint, Linter, Rule } from "eslint";

Check failure on line 1 in eng/tools/eslint-plugin-tsv/src/interfaces/named-eslint.ts

View workflow job for this annotation

GitHub Actions / Protected Files

File 'eng/tools/eslint-plugin-tsv/src/interfaces/named-eslint.ts' should only be updated by the Azure SDK team. If intentional, the PR may be merged by the Azure SDK team via bypassing the branch protections.

// ESLint with names for convenience

export namespace NamedRule {
export interface RuleModule extends Rule.RuleModule {
name: string;
}
}

export namespace NamedESLint {
export interface Plugin extends ESLint.Plugin {
configs: { recommended: Linter.Config };
name: string;
rules?: Record<string, NamedRule.RuleModule>;
}
}
59 changes: 59 additions & 0 deletions eng/tools/eslint-plugin-tsv/src/rules/emit-autorest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Ajv } from "ajv";

Check failure on line 1 in eng/tools/eslint-plugin-tsv/src/rules/emit-autorest.ts

View workflow job for this annotation

GitHub Actions / Protected Files

File 'eng/tools/eslint-plugin-tsv/src/rules/emit-autorest.ts' should only be updated by the Azure SDK team. If intentional, the PR may be merged by the Azure SDK team via bypassing the branch protections.
import { Rule } from "eslint";
import { AST, getStaticYAMLValue } from "yaml-eslint-parser";
import { TypeSpecConfigJsonSchema } from "../config/config-schema.js";
import { TypeSpecConfig } from "../config/types.js";
import { NamedRule } from "../interfaces/named-eslint.js";

export const rule: NamedRule.RuleModule = {
name: "emit-autorest",
meta: {
type: "problem",
docs: {
description:
"Requires emitter 'typespec-autorest' to be enabled by default, and requires emitted autorest to match content in repo",
},
schema: [],
messages: {
invalid: "tspconfig.yaml is invalid per the schema: {{errors}}",
missing:
'tspconfig.yaml must include the following emitter by default:\n\nemit:\n - "@azure-tools/typespec-autorest"',
// disabled: "Path does not match format '.*/specification/{orgName}/': ''{{filename}}'",
// autorestDiff: "Emitted autorest does not match content in repo",
},
},
create(context) {
return {
YAMLDocument(node: Rule.Node) {
const yamlDocument = node as unknown as AST.YAMLDocument;

// If config yaml is empty, use empty object instead of "null"
const config = getStaticYAMLValue(yamlDocument) || {};

const ajv = new Ajv();
const valid = ajv.validate(TypeSpecConfigJsonSchema, config);

if (!valid) {
context.report({
node,
messageId: "invalid",
data: { errors: ajv.errorsText(ajv.errors) },
});
return;
}

const typedConfig = config as unknown as TypeSpecConfig;
if (!typedConfig.emit?.includes("@azure-tools/typespec-autorest")) {
// TODO: Move error message to "emit:" node
context.report({
node,
messageId: "missing",
});
return;
}
},
};
},
};

export default rule;
Loading
Loading