nginx-lint-plugin 0.0.1 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,45 +1,256 @@
1
1
  # nginx-lint-plugin
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ TypeScript SDK for writing [nginx-lint](https://github.com/walf443/nginx-lint) plugins.
4
4
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
5
+ Plugins are compiled to WebAssembly Component Model modules and loaded by the nginx-lint CLI at runtime.
6
6
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
7
+ ## Install
8
8
 
9
- ## Purpose
9
+ ```bash
10
+ npm install nginx-lint-plugin
11
+ ```
10
12
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `nginx-lint-plugin`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
13
+ ## Quick Start
15
14
 
16
- ## What is OIDC Trusted Publishing?
15
+ A plugin exports two functions: `spec` (metadata) and `check` (lint logic).
17
16
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
17
+ ```typescript
18
+ // src/plugin.ts
19
+ import type { Config, LintError, PluginSpec } from "nginx-lint-plugin";
19
20
 
20
- ## Setup Instructions
21
+ export function spec(): PluginSpec {
22
+ return {
23
+ name: "my-rule",
24
+ category: "best-practices",
25
+ description: "Describe what this rule checks",
26
+ apiVersion: "1.0",
27
+ severity: "warning",
28
+ };
29
+ }
21
30
 
22
- To properly configure OIDC trusted publishing for this package:
31
+ export function check(cfg: Config, path: string): LintError[] {
32
+ const errors: LintError[] = [];
23
33
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
34
+ for (const ctx of cfg.allDirectivesWithContext()) {
35
+ const directive = ctx.directive;
28
36
 
29
- ## DO NOT USE THIS PACKAGE
37
+ if (directive.is("proxy_pass") && !directive.hasBlock()) {
38
+ errors.push({
39
+ rule: "my-rule",
40
+ category: "best-practices",
41
+ message: "proxy_pass should ...",
42
+ severity: "warning",
43
+ line: directive.line(),
44
+ column: directive.column(),
45
+ fixes: [directive.replaceWith("proxy_pass http://upstream;")],
46
+ });
47
+ }
48
+ }
30
49
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
50
+ return errors;
51
+ }
52
+ ```
36
53
 
37
- ## More Information
54
+ ## Testing
38
55
 
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
56
+ The `nginx-lint-plugin/testing` entry provides parser-based testing utilities. Tests parse real nginx configuration strings using the same Rust parser that powers the production linter.
42
57
 
43
- ---
58
+ ```typescript
59
+ import { describe, it } from "node:test";
60
+ import { spec, check } from "./plugin.js";
61
+ import { parseConfig, PluginTestRunner } from "nginx-lint-plugin/testing";
44
62
 
45
- **Maintained for OIDC setup purposes only**
63
+ describe("my-rule", () => {
64
+ const runner = new PluginTestRunner(spec, check);
65
+
66
+ it("detects the issue", () => {
67
+ runner.assertErrors("http {\n proxy_pass http://bad;\n}", 1);
68
+ });
69
+
70
+ it("passes valid config", () => {
71
+ runner.assertErrors("http {\n proxy_pass http://good;\n}", 0);
72
+ });
73
+
74
+ it("checks error on specific line", () => {
75
+ runner.assertErrorOnLine("http {\n proxy_pass http://bad;\n}", 2);
76
+ });
77
+
78
+ it("validates bad/good examples", () => {
79
+ runner.testExamples(
80
+ "http {\n proxy_pass http://bad;\n}",
81
+ "http {\n proxy_pass http://good;\n}",
82
+ );
83
+ });
84
+ });
85
+ ```
86
+
87
+ ### Testing with include context
88
+
89
+ Use `parseConfig` directly to simulate files included from specific blocks:
90
+
91
+ ```typescript
92
+ it("handles included files", () => {
93
+ const cfg = parseConfig("server_tokens off;", {
94
+ includeContext: ["http", "server"],
95
+ });
96
+ const errors = check(cfg, "test.conf");
97
+ assert.equal(errors.length, 0);
98
+ });
99
+ ```
100
+
101
+ ### PluginTestRunner
102
+
103
+ | Method | Description |
104
+ |--------|-------------|
105
+ | `checkString(content, opts?)` | Parse and check, returning errors from this rule |
106
+ | `assertErrors(content, count)` | Assert exactly N errors |
107
+ | `assertErrorOnLine(content, line)` | Assert error on a specific line |
108
+ | `testExamples(badConf, goodConf)` | Validate bad config produces errors, good config does not |
109
+
110
+ ## Building a Plugin
111
+
112
+ ### package.json
113
+
114
+ The WIT definition file is bundled with this package, so `jco componentize` can reference it directly from `node_modules`.
115
+
116
+ ```json
117
+ {
118
+ "name": "my-plugin",
119
+ "type": "module",
120
+ "scripts": {
121
+ "build": "tsc && jco componentize dist/plugin.js -w node_modules/nginx-lint-plugin/wit -n plugin --disable all -o dist/my-plugin.wasm",
122
+ "test": "tsc && node --test dist/plugin.test.js"
123
+ },
124
+ "dependencies": {
125
+ "nginx-lint-plugin": "^0.8.3"
126
+ },
127
+ "devDependencies": {
128
+ "@bytecodealliance/componentize-js": "^0.19",
129
+ "@bytecodealliance/jco": "^1",
130
+ "typescript": "^5"
131
+ }
132
+ }
133
+ ```
134
+
135
+ ### tsconfig.json
136
+
137
+ ```json
138
+ {
139
+ "compilerOptions": {
140
+ "target": "ES2022",
141
+ "module": "ES2022",
142
+ "moduleResolution": "bundler",
143
+ "outDir": "dist",
144
+ "strict": true,
145
+ "skipLibCheck": true,
146
+ "declaration": true
147
+ },
148
+ "include": ["src"]
149
+ }
150
+ ```
151
+
152
+ ### Build and run
153
+
154
+ ```bash
155
+ npm run build
156
+ nginx-lint --plugins ./dist path/to/nginx.conf
157
+ ```
158
+
159
+ The `--plugins` option takes a directory path. nginx-lint automatically loads all `.wasm` files found in that directory.
160
+
161
+ ## API Reference
162
+
163
+ ### Types
164
+
165
+ ```typescript
166
+ import type {
167
+ // Core types
168
+ Severity, // "error" | "warning"
169
+ Fix, // Autofix descriptor
170
+ LintError, // Lint error with rule, message, line, column, fixes
171
+ PluginSpec, // Plugin metadata
172
+
173
+ // Directive data
174
+ ArgumentType, // "literal" | "quoted-string" | "single-quoted-string" | "variable"
175
+ ArgumentInfo, // Argument with value, raw text, type, position
176
+ DirectiveData, // Flat directive properties
177
+
178
+ // Config tree
179
+ Config, // Parsed nginx configuration
180
+ Directive, // A single directive with methods
181
+ DirectiveContext,// Directive with parent stack and depth
182
+ ConfigItem, // Directive | Comment | BlankLine
183
+ } from "nginx-lint-plugin";
184
+ ```
185
+
186
+ ### Config
187
+
188
+ | Method | Description |
189
+ |--------|-------------|
190
+ | `allDirectivesWithContext()` | All directives with parent context (DFS order) |
191
+ | `allDirectives()` | All directives without context |
192
+ | `items()` | Top-level config items (directives, comments, blank lines) |
193
+ | `includeContext()` | Parent block names from `include` directives |
194
+ | `isIncludedFrom(context)` | Check if included from a specific block |
195
+ | `isIncludedFromHttp()` | Check if included from `http` block |
196
+ | `isIncludedFromHttpServer()` | Check if included from `http > server` |
197
+ | `isIncludedFromHttpLocation()` | Check if included from `http > ... > location` |
198
+ | `isIncludedFromStream()` | Check if included from `stream` block |
199
+ | `immediateParentContext()` | Immediate parent block name |
200
+
201
+ ### Directive
202
+
203
+ | Method | Description |
204
+ |--------|-------------|
205
+ | `name()` | Directive name (e.g. `"server_tokens"`) |
206
+ | `is(name)` | Check directive name |
207
+ | `firstArg()` | First argument value |
208
+ | `firstArgIs(value)` | Check first argument |
209
+ | `argAt(index)` | Argument at index |
210
+ | `lastArg()` | Last argument value |
211
+ | `hasArg(value)` | Check if argument exists |
212
+ | `argCount()` | Number of arguments |
213
+ | `args()` | All arguments as `ArgumentInfo[]` |
214
+ | `line()` / `column()` | Source position |
215
+ | `hasBlock()` | Whether directive has a `{ }` block |
216
+ | `blockItems()` | Child items inside the block |
217
+ | `blockIsRaw()` | Whether block content is raw (e.g. `map`) |
218
+
219
+ ### Directive Fix Builders
220
+
221
+ | Method | Description |
222
+ |--------|-------------|
223
+ | `replaceWith(newText)` | Replace the entire directive |
224
+ | `deleteLineFix()` | Delete the directive's line |
225
+ | `insertAfter(newText)` | Insert text after the directive |
226
+ | `insertBefore(newText)` | Insert text before the directive |
227
+ | `insertAfterMany(lines)` | Insert multiple lines after |
228
+ | `insertBeforeMany(lines)` | Insert multiple lines before |
229
+
230
+ ### DirectiveContext
231
+
232
+ | Field | Description |
233
+ |-------|-------------|
234
+ | `directive` | The directive |
235
+ | `parentStack` | Parent block names (e.g. `["http", "server"]`) |
236
+ | `depth` | Nesting depth |
237
+
238
+ ### PluginSpec
239
+
240
+ ```typescript
241
+ {
242
+ name: string; // Rule identifier (e.g. "my-rule")
243
+ category: string; // Category (e.g. "security", "best-practices", "style", "syntax")
244
+ description: string; // Human-readable description
245
+ apiVersion: string; // API version ("1.0")
246
+ severity?: string; // Default: "warning". Also accepts "error"
247
+ why?: string; // Explanation of why this rule matters
248
+ badExample?: string; // Config that triggers the rule
249
+ goodExample?: string; // Config that passes the rule
250
+ references?: string[];// Links to relevant documentation
251
+ }
252
+ ```
253
+
254
+ ## License
255
+
256
+ MIT
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Builds WIT-compatible Config/Directive objects from parser component output.
3
+ *
4
+ * The parser WASM component returns a ParseOutput with an index-based tree
5
+ * representation (to avoid recursive types in WIT). This module reconstructs
6
+ * the method-based Directive/Config interfaces from that flat representation.
7
+ */
8
+ import type { Config } from "./generated/interfaces/nginx-lint-plugin-config-api.js";
9
+ import type { ParseOutput } from "../wasm/parser/interfaces/nginx-lint-plugin-parser-types.js";
10
+ export declare function buildConfigFromParseOutput(output: ParseOutput): Config;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Builds WIT-compatible Config/Directive objects from parser component output.
3
+ *
4
+ * The parser WASM component returns a ParseOutput with an index-based tree
5
+ * representation (to avoid recursive types in WIT). This module reconstructs
6
+ * the method-based Directive/Config interfaces from that flat representation.
7
+ */
8
+ import { makeIncludeContextMethods } from "./include-context.js";
9
+ // ── Wrap DirectiveData with Directive interface ─────────────────────
10
+ function wrapDirective(data, resolveBlockItems) {
11
+ const argValues = data.args.map((a) => a.value);
12
+ return {
13
+ data() { return data; },
14
+ name() { return data.name; },
15
+ is(name) { return data.name === name; },
16
+ firstArg() { return argValues[0] ?? undefined; },
17
+ firstArgIs(value) { return argValues[0] === value; },
18
+ argAt(index) { return argValues[index] ?? undefined; },
19
+ lastArg() { return argValues.length > 0 ? argValues[argValues.length - 1] : undefined; },
20
+ hasArg(value) { return argValues.includes(value); },
21
+ argCount() { return argValues.length; },
22
+ args() { return data.args; },
23
+ line() { return data.line; },
24
+ column() { return data.column; },
25
+ startOffset() { return data.startOffset; },
26
+ endOffset() { return data.endOffset; },
27
+ leadingWhitespace() { return data.leadingWhitespace; },
28
+ trailingWhitespace() { return data.trailingWhitespace; },
29
+ spaceBeforeTerminator() { return data.spaceBeforeTerminator; },
30
+ hasBlock() { return data.hasBlock; },
31
+ blockItems() { return resolveBlockItems(); },
32
+ blockIsRaw() { return data.blockIsRaw; },
33
+ replaceWith(newText) {
34
+ return {
35
+ line: data.line, oldText: undefined, newText,
36
+ deleteLine: false, insertAfter: false,
37
+ startOffset: data.startOffset, endOffset: data.endOffset,
38
+ };
39
+ },
40
+ deleteLineFix() {
41
+ return {
42
+ line: data.line, oldText: undefined, newText: "",
43
+ deleteLine: true, insertAfter: false,
44
+ startOffset: undefined, endOffset: undefined,
45
+ };
46
+ },
47
+ insertAfter(newText) {
48
+ return {
49
+ line: data.line, oldText: undefined, newText,
50
+ deleteLine: false, insertAfter: true,
51
+ startOffset: undefined, endOffset: undefined,
52
+ };
53
+ },
54
+ insertBefore(newText) {
55
+ return {
56
+ line: data.line, oldText: undefined, newText,
57
+ deleteLine: false, insertAfter: false,
58
+ startOffset: undefined, endOffset: undefined,
59
+ };
60
+ },
61
+ insertAfterMany(lines) {
62
+ return {
63
+ line: data.line, oldText: undefined, newText: lines.join("\n"),
64
+ deleteLine: false, insertAfter: true,
65
+ startOffset: undefined, endOffset: undefined,
66
+ };
67
+ },
68
+ insertBeforeMany(lines) {
69
+ return {
70
+ line: data.line, oldText: undefined, newText: lines.join("\n"),
71
+ deleteLine: false, insertAfter: false,
72
+ startOffset: undefined, endOffset: undefined,
73
+ };
74
+ },
75
+ };
76
+ }
77
+ // ── Resolve index-based items to ConfigItem tree ────────────────────
78
+ function resolveConfigItem(allItems, index) {
79
+ const item = allItems[index];
80
+ if (item.value.tag === "directive-item") {
81
+ const data = item.value.val;
82
+ const childIndices = item.childIndices;
83
+ return {
84
+ tag: "directive-item",
85
+ val: wrapDirective(data, () => Array.from(childIndices).map((i) => resolveConfigItem(allItems, i))),
86
+ };
87
+ }
88
+ if (item.value.tag === "comment-item") {
89
+ return { tag: "comment-item", val: item.value.val };
90
+ }
91
+ return { tag: "blank-line-item", val: item.value.val };
92
+ }
93
+ // ── Build Config from ParseOutput ───────────────────────────────────
94
+ export function buildConfigFromParseOutput(output) {
95
+ const inclCtx = output.includeContext;
96
+ const allItems = output.allItems;
97
+ const directiveContexts = output.directivesWithContext.map((ctx) => ({
98
+ directive: wrapDirective(ctx.data, () => Array.from(ctx.blockItemIndices).map((i) => resolveConfigItem(allItems, i))),
99
+ parentStack: ctx.parentStack,
100
+ depth: ctx.depth,
101
+ }));
102
+ const topLevelItems = Array.from(output.topLevelIndices).map((i) => resolveConfigItem(allItems, i));
103
+ return {
104
+ allDirectivesWithContext() { return directiveContexts; },
105
+ allDirectives() { return directiveContexts.map((c) => c.directive); },
106
+ items() { return topLevelItems; },
107
+ ...makeIncludeContextMethods(inclCtx),
108
+ };
109
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Shared include-context helpers for Config implementations.
3
+ *
4
+ * Provides the include-context portion of the Config interface so that
5
+ * config-builder.ts can reuse a single, tested implementation.
6
+ */
7
+ export interface IncludeContextMethods {
8
+ includeContext(): string[];
9
+ isIncludedFrom(context: string): boolean;
10
+ isIncludedFromHttp(): boolean;
11
+ isIncludedFromHttpServer(): boolean;
12
+ isIncludedFromHttpLocation(): boolean;
13
+ isIncludedFromStream(): boolean;
14
+ immediateParentContext(): string | undefined;
15
+ }
16
+ export declare function makeIncludeContextMethods(inclCtx: string[]): IncludeContextMethods;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Shared include-context helpers for Config implementations.
3
+ *
4
+ * Provides the include-context portion of the Config interface so that
5
+ * config-builder.ts can reuse a single, tested implementation.
6
+ */
7
+ export function makeIncludeContextMethods(inclCtx) {
8
+ return {
9
+ includeContext() { return inclCtx; },
10
+ isIncludedFrom(context) { return inclCtx.includes(context); },
11
+ isIncludedFromHttp() { return inclCtx.includes("http"); },
12
+ isIncludedFromHttpServer() {
13
+ const httpIndex = inclCtx.indexOf("http");
14
+ const serverIndex = inclCtx.indexOf("server");
15
+ return httpIndex !== -1 && serverIndex !== -1 && httpIndex < serverIndex;
16
+ },
17
+ isIncludedFromHttpLocation() {
18
+ const httpIndex = inclCtx.indexOf("http");
19
+ const locationIndex = inclCtx.indexOf("location");
20
+ return httpIndex !== -1 && locationIndex !== -1 && httpIndex < locationIndex;
21
+ },
22
+ isIncludedFromStream() { return inclCtx.includes("stream"); },
23
+ immediateParentContext() { return inclCtx.length > 0 ? inclCtx[inclCtx.length - 1] : undefined; },
24
+ };
25
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * nginx-lint-plugin — shared TypeScript library for nginx-lint WASM plugins.
3
+ *
4
+ * Types are auto-generated from the WIT definition (wit/nginx-lint-plugin.wit)
5
+ * by `jco types` during the build step.
6
+ *
7
+ * Usage:
8
+ * import type { Config, LintError, PluginSpec } from "nginx-lint-plugin";
9
+ */
10
+ export type { Severity, Fix, LintError, PluginSpec, } from "./generated/interfaces/nginx-lint-plugin-types.js";
11
+ export type { ArgumentType, ArgumentInfo, CommentInfo, BlankLineInfo, DirectiveData, } from "./generated/interfaces/nginx-lint-plugin-data-types.js";
12
+ export type { ConfigItem, ConfigItemDirectiveItem, ConfigItemCommentItem, ConfigItemBlankLineItem, DirectiveContext, Directive, Config, } from "./generated/interfaces/nginx-lint-plugin-config-api.js";
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * nginx-lint-plugin — shared TypeScript library for nginx-lint WASM plugins.
3
+ *
4
+ * Types are auto-generated from the WIT definition (wit/nginx-lint-plugin.wit)
5
+ * by `jco types` during the build step.
6
+ *
7
+ * Usage:
8
+ * import type { Config, LintError, PluginSpec } from "nginx-lint-plugin";
9
+ */
10
+ export {};
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Parser-based testing utilities for TypeScript nginx-lint plugins.
3
+ *
4
+ * Provides `parseConfig()` to parse real nginx configuration strings
5
+ * into WIT-compatible Config objects, and `PluginTestRunner` for
6
+ * assertion-based testing.
7
+ *
8
+ * Usage:
9
+ * import { parseConfig, PluginTestRunner } from "nginx-lint-plugin/testing";
10
+ */
11
+ import type { Config } from "./generated/interfaces/nginx-lint-plugin-config-api.js";
12
+ import type { LintError, PluginSpec } from "./generated/interfaces/nginx-lint-plugin-types.js";
13
+ /**
14
+ * Parse an nginx configuration string into a WIT-compatible Config object.
15
+ *
16
+ * Uses the nginx-lint-parser WASM component for accurate parsing identical
17
+ * to the production Rust parser. The DFS traversal (allDirectivesWithContext)
18
+ * is computed on the Rust side.
19
+ */
20
+ export declare function parseConfig(source: string, opts?: {
21
+ includeContext?: string[];
22
+ }): Config;
23
+ type SpecFn = () => PluginSpec;
24
+ type CheckFn = (cfg: Config, path: string) => LintError[];
25
+ /**
26
+ * Test runner for TypeScript nginx-lint plugins.
27
+ *
28
+ * Mirrors the Rust `PluginTestRunner` API from `nginx-lint-plugin/testing`.
29
+ *
30
+ * Example:
31
+ * ```typescript
32
+ * import { spec, check } from "./plugin.js";
33
+ * import { PluginTestRunner } from "nginx-lint-plugin/testing";
34
+ *
35
+ * const runner = new PluginTestRunner(spec, check);
36
+ * runner.assertErrors("http { server_tokens on; }", 1);
37
+ * runner.assertErrors("http { server_tokens off; }", 0);
38
+ * ```
39
+ */
40
+ export declare class PluginTestRunner {
41
+ private specFn;
42
+ private checkFn;
43
+ constructor(spec: SpecFn, check: CheckFn);
44
+ /**
45
+ * Parse and check a config string, returning only errors from this plugin's rule.
46
+ */
47
+ checkString(content: string, opts?: {
48
+ includeContext?: string[];
49
+ }): LintError[];
50
+ /**
51
+ * Assert that parsing and checking a config produces exactly `count` errors
52
+ * from this plugin's rule.
53
+ */
54
+ assertErrors(content: string, count: number): void;
55
+ /**
56
+ * Assert that a config produces at least one error on the given line.
57
+ */
58
+ assertErrorOnLine(content: string, line: number): void;
59
+ /**
60
+ * Test bad/good config content.
61
+ * - `badConf` must produce at least one error.
62
+ * - `goodConf` must produce zero errors.
63
+ */
64
+ testExamples(badConf: string, goodConf: string): void;
65
+ }
66
+ export {};
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Parser-based testing utilities for TypeScript nginx-lint plugins.
3
+ *
4
+ * Provides `parseConfig()` to parse real nginx configuration strings
5
+ * into WIT-compatible Config objects, and `PluginTestRunner` for
6
+ * assertion-based testing.
7
+ *
8
+ * Usage:
9
+ * import { parseConfig, PluginTestRunner } from "nginx-lint-plugin/testing";
10
+ */
11
+ import { parseConfig as parseConfigWasm } from "../wasm/parser/parser.js";
12
+ import { buildConfigFromParseOutput } from "./config-builder.js";
13
+ /**
14
+ * Parse an nginx configuration string into a WIT-compatible Config object.
15
+ *
16
+ * Uses the nginx-lint-parser WASM component for accurate parsing identical
17
+ * to the production Rust parser. The DFS traversal (allDirectivesWithContext)
18
+ * is computed on the Rust side.
19
+ */
20
+ export function parseConfig(source, opts) {
21
+ const output = parseConfigWasm(source, opts?.includeContext ?? []);
22
+ return buildConfigFromParseOutput(output);
23
+ }
24
+ /**
25
+ * Test runner for TypeScript nginx-lint plugins.
26
+ *
27
+ * Mirrors the Rust `PluginTestRunner` API from `nginx-lint-plugin/testing`.
28
+ *
29
+ * Example:
30
+ * ```typescript
31
+ * import { spec, check } from "./plugin.js";
32
+ * import { PluginTestRunner } from "nginx-lint-plugin/testing";
33
+ *
34
+ * const runner = new PluginTestRunner(spec, check);
35
+ * runner.assertErrors("http { server_tokens on; }", 1);
36
+ * runner.assertErrors("http { server_tokens off; }", 0);
37
+ * ```
38
+ */
39
+ export class PluginTestRunner {
40
+ specFn;
41
+ checkFn;
42
+ constructor(spec, check) {
43
+ this.specFn = spec;
44
+ this.checkFn = check;
45
+ }
46
+ /**
47
+ * Parse and check a config string, returning only errors from this plugin's rule.
48
+ */
49
+ checkString(content, opts) {
50
+ const cfg = parseConfig(content, opts);
51
+ const errors = this.checkFn(cfg, "test.conf");
52
+ const ruleName = this.specFn().name;
53
+ return errors.filter((e) => e.rule === ruleName);
54
+ }
55
+ /**
56
+ * Assert that parsing and checking a config produces exactly `count` errors
57
+ * from this plugin's rule.
58
+ */
59
+ assertErrors(content, count) {
60
+ const errors = this.checkString(content);
61
+ if (errors.length !== count) {
62
+ throw new Error(`Expected ${count} error(s) from "${this.specFn().name}", got ${errors.length}: ${JSON.stringify(errors, null, 2)}`);
63
+ }
64
+ }
65
+ /**
66
+ * Assert that a config produces at least one error on the given line.
67
+ */
68
+ assertErrorOnLine(content, line) {
69
+ const errors = this.checkString(content);
70
+ const hasLine = errors.some((e) => e.line === line);
71
+ if (!hasLine) {
72
+ const lines = errors.map((e) => e.line);
73
+ throw new Error(`Expected error on line ${line} from "${this.specFn().name}", got errors on lines: ${JSON.stringify(lines)}`);
74
+ }
75
+ }
76
+ /**
77
+ * Test bad/good config content.
78
+ * - `badConf` must produce at least one error.
79
+ * - `goodConf` must produce zero errors.
80
+ */
81
+ testExamples(badConf, goodConf) {
82
+ const ruleName = this.specFn().name;
83
+ const badErrors = this.checkString(badConf);
84
+ if (badErrors.length === 0) {
85
+ throw new Error(`bad.conf should produce at least one "${ruleName}" error, got none`);
86
+ }
87
+ const goodErrors = this.checkString(goodConf);
88
+ if (goodErrors.length > 0) {
89
+ throw new Error(`good.conf should produce no "${ruleName}" errors, got: ${JSON.stringify(goodErrors, null, 2)}`);
90
+ }
91
+ }
92
+ }
package/package.json CHANGED
@@ -1,10 +1,32 @@
1
1
  {
2
2
  "name": "nginx-lint-plugin",
3
- "version": "0.0.1",
4
- "description": "OIDC trusted publishing setup package for nginx-lint-plugin",
5
- "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
9
- ]
3
+ "version": "0.8.3",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/walf443/nginx-lint",
9
+ "directory": "plugins/typescript/nginx-lint-plugin"
10
+ },
11
+ "main": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "exports": {
14
+ ".": "./dist/index.js",
15
+ "./testing": "./dist/plugin-test-runner.js"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "wasm",
20
+ "wit"
21
+ ],
22
+ "scripts": {
23
+ "copy-wit": "mkdir -p wit && cp ../../../wit/nginx-lint-plugin.wit wit/",
24
+ "generate": "jco types wit -n plugin -o src/generated",
25
+ "build": "npm run copy-wit && npm run generate && tsc",
26
+ "prepublishOnly": "npm run build"
27
+ },
28
+ "devDependencies": {
29
+ "@bytecodealliance/jco": "^1",
30
+ "typescript": "^5"
31
+ }
10
32
  }