Skip to content

Commit 5571cd2

Browse files
authored
Merge pull request #35 from glecaros/glecaros/suppress-emitter
feat(muzzle): Add support to suppressing emitter diagnostics.
2 parents 76ef317 + 61b49da commit 5571cd2

6 files changed

Lines changed: 123 additions & 14 deletions

File tree

@binkylabs/muzzle/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"format": "prettier ./src/*.ts --write"
3535
},
3636
"devDependencies": {
37+
"@azure-tools/typespec-autorest": "^0.63.1",
3738
"@azure-tools/typespec-azure-rulesets": "^0.63.0",
3839
"@eslint/js": "^9.39.2",
3940
"@types/node": "^25.0.3",

@binkylabs/muzzle/src/cli.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Arguments:
1717
1818
Options:
1919
-r, --rule-set <ruleset> Specify a rule set to apply (can be used multiple times)
20+
-e, --emitter <emitter> Specify an emitter to apply (can be used multiple times)
2021
-m, --message <message> Suppression message to add to all suppressions
2122
-h, --help Show this help message
2223
@@ -30,6 +31,7 @@ Examples:
3031
function parseCliArguments(args: string[]): SuppressionOptions {
3132
let entryPoint: string | undefined;
3233
const ruleSets: `${string}/${string}`[] = [];
34+
const emitters: string[] = [];
3335
let message: string | undefined;
3436

3537
for (let i = 0; i < args.length; ) {
@@ -46,6 +48,14 @@ function parseCliArguments(args: string[]): SuppressionOptions {
4648
}
4749
ruleSets.push(ruleSet as `${string}/${string}`);
4850
i += 2; // Skip both the flag and its value
51+
} else if (arg === "--emitter" || arg === "-e") {
52+
const emitter = args[i + 1];
53+
if (!emitter || emitter.startsWith("-")) {
54+
console.error(`Error: ${arg} requires a value`);
55+
process.exit(1);
56+
}
57+
emitters.push(emitter);
58+
i += 2; // Skip both the flag and its value
4959
} else if (arg === "--message" || arg === "-m") {
5060
const messageValue = args[i + 1];
5161
if (!messageValue || messageValue.startsWith("-")) {
@@ -66,7 +76,7 @@ function parseCliArguments(args: string[]): SuppressionOptions {
6676
}
6777
}
6878

69-
return { entryPoint: entryPoint || "", ruleSets, message };
79+
return { entryPoint: entryPoint || "", emitters, ruleSets, message };
7080
}
7181

7282
const options = parseCliArguments(process.argv.slice(2));

@binkylabs/muzzle/src/index.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,50 @@ model FooBarArray is (Foo | Bar)[];
148148
// Verify the output matches expected
149149
expect(result.trim()).toBe(expectedOutput.trim());
150150
});
151+
152+
it("should add suppress directives related to warnings generated by an emitter", async () => {
153+
const inputTypeSpec = `
154+
model Foo {
155+
bar: "one" | integer;
156+
}
157+
`;
158+
159+
const expectedOutput = `
160+
model Foo {
161+
#suppress "@azure-tools/typespec-autorest/union-unsupported" "Auto-suppressed warnings non-applicable rules during import."
162+
bar: "one" | integer;
163+
}
164+
165+
`;
166+
167+
// Write the test TypeSpec file
168+
writeFileSync(testFilePath, inputTypeSpec);
169+
170+
// Compile the TypeSpec program with linting rules
171+
const [options] = await resolveCompilerOptions(NodeHost, {
172+
cwd: testDir,
173+
entrypoint: testFilePath,
174+
overrides: {
175+
emit: ["@azure-tools/typespec-autorest"],
176+
},
177+
});
178+
179+
const program = await compile(NodeHost, testFilePath, options);
180+
181+
// Apply suppressions
182+
await suppressEverything(program, {
183+
message: "Auto-suppressed warnings non-applicable rules during import.",
184+
});
185+
186+
// Format the file
187+
const sourceCode = await NodeHost.readFile(testFilePath);
188+
const formattedSource = await formatTypeSpec(sourceCode.text);
189+
await NodeHost.writeFile(testFilePath, formattedSource);
190+
191+
// Read the modified file
192+
const result = readFileSync(testFilePath, "utf-8");
193+
194+
// Verify the output matches expected
195+
expect(result.trim()).toBe(expectedOutput.trim());
196+
});
151197
});

@binkylabs/muzzle/src/index.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { existsSync } from "node:fs";
22
import {
3-
compile,
3+
compile as typespecCompile,
44
createSuppressCodeFix,
55
DiagnosticTarget,
66
NodeHost,
@@ -9,6 +9,7 @@ import {
99
resolveCompilerOptions,
1010
applyCodeFixes,
1111
formatTypeSpec,
12+
CompilerOptions,
1213
} from "@typespec/compiler";
1314

1415
import { findSuppressTarget } from "./typespec-imports.js";
@@ -54,6 +55,22 @@ export async function suppressEverything(
5455
await applyCodeFixes(p.host, codeFixes);
5556
}
5657

58+
async function compile(
59+
entryPoint: string,
60+
compilerOptions: CompilerOptions,
61+
): Promise<Program> {
62+
/* We prevent the compiler from writing files to disk by overriding the writeFile method on the NodeHost. */
63+
const originalWriteFile = NodeHost.writeFile;
64+
try {
65+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
66+
NodeHost.writeFile = (_path: string, _content: string) => Promise.resolve();
67+
return await typespecCompile(NodeHost, entryPoint, compilerOptions);
68+
} finally {
69+
/* Restore the original writeFile method after compilation. */
70+
NodeHost.writeFile = originalWriteFile;
71+
}
72+
}
73+
5774
async function formatSourceFile(filePath: string) {
5875
const sourceCode = await NodeHost.readFile(filePath);
5976
const formattedSource = await formatTypeSpec(sourceCode.text);
@@ -66,8 +83,8 @@ async function formatSourceFile(filePath: string) {
6683
export async function parseTypeSpecAndSuppressEverything(
6784
options: SuppressionOptions,
6885
) {
69-
if (options.ruleSets.length === 0) {
70-
throw new Error("At least one rule set must be provided.");
86+
if (options.ruleSets.length === 0 && options.emitters.length === 0) {
87+
throw new Error("At least one rule set or emitter must be provided.");
7188
}
7289

7390
if (!options.entryPoint) {
@@ -85,14 +102,15 @@ export async function parseTypeSpecAndSuppressEverything(
85102
cwd: process.cwd(),
86103
entrypoint: options.entryPoint,
87104
overrides: {
105+
emit: options.emitters,
88106
linter: {
89107
extends: options.ruleSets,
90108
},
91109
},
92110
});
93111

94112
// Create the TypeSpec program
95-
const program = await compile(NodeHost, options.entryPoint, compilerOptions);
113+
const program = await compile(options.entryPoint, compilerOptions);
96114

97115
if (
98116
program.diagnostics.some(
@@ -117,8 +135,10 @@ export async function parseTypeSpecAndSuppressEverything(
117135
export interface SuppressionOptions {
118136
/** The entry point file for the TypeSpec program */
119137
entryPoint: string;
120-
/** The rule sets to apply. At least one rule set must be provided. */
138+
/** The rule sets to apply. At least one rule or one emitter must be provided. */
121139
ruleSets: `${string}/${string}`[];
140+
/** The emitters to apply. At least one rule or one emitter must be provided. */
141+
emitters: string[];
122142
/** The message to include with each suppression directive */
123143
message?: string;
124144
}

README.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,20 @@ Welcome to the TypeSpec Muzzle project. This project aims to provide tooling to
1212
npm i -g @binkylabs/muzzle
1313
```
1414

15-
1. Ensure the ruleset you want to suppress is also installed
15+
1. Ensure the ruleset or emitter you want to suppress is also installed
1616

1717
```shell
18-
# example you use the @typespec/http/recommended ruleset
19-
npm i @typespec/http
18+
# example you use the @typespec/http/recommended ruleset and the @azure-tools/typespec-autorest emitter
19+
npm i @typespec/http @azure-tools/typespec-autorest
2020
```
2121

2222
1. Suppress all warnings generated by the ruleset
2323

2424
```shell
2525
muzzle main.tsp --rule-set "@typespec/http/recommended" -m "auto-suppression"
26+
27+
# Or for an emitter
28+
muzzle main.tsp --emiter "@azure-tools/typespec-autorest"
2629
```
2730

2831
### API
@@ -36,8 +39,8 @@ Welcome to the TypeSpec Muzzle project. This project aims to provide tooling to
3639
1. Ensure the ruleset you want to suppress is also installed
3740

3841
```shell
39-
# example you use the @typespec/http/recommended ruleset
40-
npm i -S @typespec/http
42+
# example you use the @typespec/http/recommended ruleset and the @azure-tools/typespec-autorest
43+
npm i -S @typespec/http @azure-tools/typespec-autorest
4144
```
4245

4346
1. Use the suppression method
@@ -49,6 +52,7 @@ Welcome to the TypeSpec Muzzle project. This project aims to provide tooling to
4952
{
5053
entryPoint: "path/to/main.tsp",
5154
ruleSets: ["@typespec/http/recommended"],
55+
emitters: ["@azure-tools/typespec-autorest"],
5256
message: "auto-suppression"
5357
}
5458
);

package-lock.json

Lines changed: 31 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)