nginx-lint-plugin 0.0.1 → 0.8.2
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 +239 -28
- package/dist/config-builder.d.ts +10 -0
- package/dist/config-builder.js +109 -0
- package/dist/include-context.d.ts +16 -0
- package/dist/include-context.js +25 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +10 -0
- package/dist/plugin-test-runner.d.ts +66 -0
- package/dist/plugin-test-runner.js +92 -0
- package/package.json +28 -7
- package/wit/nginx-lint-plugin.wit +281 -0
package/README.md
CHANGED
|
@@ -1,45 +1,256 @@
|
|
|
1
1
|
# nginx-lint-plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript SDK for writing [nginx-lint](https://github.com/walf443/nginx-lint) plugins.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Plugins are compiled to WebAssembly Component Model modules and loaded by the nginx-lint CLI at runtime.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Install
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
```bash
|
|
10
|
+
npm install nginx-lint-plugin
|
|
11
|
+
```
|
|
10
12
|
|
|
11
|
-
|
|
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
|
-
|
|
15
|
+
A plugin exports two functions: `spec` (metadata) and `check` (lint logic).
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
```typescript
|
|
18
|
+
// src/plugin.ts
|
|
19
|
+
import type { Config, LintError, PluginSpec } from "nginx-lint-plugin";
|
|
19
20
|
|
|
20
|
-
|
|
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
|
-
|
|
31
|
+
export function check(cfg: Config, path: string): LintError[] {
|
|
32
|
+
const errors: LintError[] = [];
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- Should not be installed as a dependency
|
|
35
|
-
- Exists only for administrative purposes
|
|
50
|
+
return errors;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
36
53
|
|
|
37
|
-
##
|
|
54
|
+
## Testing
|
|
38
55
|
|
|
39
|
-
|
|
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
|
-
|
|
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.2"
|
|
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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nginx-lint-plugin",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
3
|
+
"version": "0.8.2",
|
|
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
|
+
"wit"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"copy-wit": "mkdir -p wit && cp ../../../wit/nginx-lint-plugin.wit wit/",
|
|
23
|
+
"generate": "jco types wit -n plugin -o src/generated",
|
|
24
|
+
"build": "npm run copy-wit && npm run generate && tsc",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@bytecodealliance/jco": "^1",
|
|
29
|
+
"typescript": "^5"
|
|
30
|
+
}
|
|
10
31
|
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
package nginx-lint:plugin@3.0.0;
|
|
2
|
+
|
|
3
|
+
interface types {
|
|
4
|
+
enum severity {
|
|
5
|
+
error,
|
|
6
|
+
warning,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
record fix {
|
|
10
|
+
line: u32,
|
|
11
|
+
old-text: option<string>,
|
|
12
|
+
new-text: string,
|
|
13
|
+
delete-line: bool,
|
|
14
|
+
insert-after: bool,
|
|
15
|
+
start-offset: option<u32>,
|
|
16
|
+
end-offset: option<u32>,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
record lint-error {
|
|
20
|
+
rule: string,
|
|
21
|
+
category: string,
|
|
22
|
+
message: string,
|
|
23
|
+
severity: severity,
|
|
24
|
+
line: option<u32>,
|
|
25
|
+
column: option<u32>,
|
|
26
|
+
fixes: list<fix>,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
record plugin-spec {
|
|
30
|
+
name: string,
|
|
31
|
+
category: string,
|
|
32
|
+
description: string,
|
|
33
|
+
api-version: string,
|
|
34
|
+
severity: option<string>,
|
|
35
|
+
why: option<string>,
|
|
36
|
+
bad-example: option<string>,
|
|
37
|
+
good-example: option<string>,
|
|
38
|
+
references: option<list<string>>,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Shared data types used by both the plugin config-api (resource-based)
|
|
43
|
+
/// and the parser output (record-based).
|
|
44
|
+
interface data-types {
|
|
45
|
+
/// Argument value type
|
|
46
|
+
enum argument-type {
|
|
47
|
+
literal,
|
|
48
|
+
quoted-string,
|
|
49
|
+
single-quoted-string,
|
|
50
|
+
variable,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Argument data (returned as a record since it's small)
|
|
54
|
+
record argument-info {
|
|
55
|
+
value: string,
|
|
56
|
+
raw: string,
|
|
57
|
+
arg-type: argument-type,
|
|
58
|
+
line: u32,
|
|
59
|
+
column: u32,
|
|
60
|
+
start-offset: u32,
|
|
61
|
+
end-offset: u32,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Comment data
|
|
65
|
+
record comment-info {
|
|
66
|
+
text: string,
|
|
67
|
+
line: u32,
|
|
68
|
+
column: u32,
|
|
69
|
+
leading-whitespace: string,
|
|
70
|
+
trailing-whitespace: string,
|
|
71
|
+
start-offset: u32,
|
|
72
|
+
end-offset: u32,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Blank line data
|
|
76
|
+
record blank-line-info {
|
|
77
|
+
line: u32,
|
|
78
|
+
content: string,
|
|
79
|
+
start-offset: u32,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// All flat properties of a directive (for bulk retrieval)
|
|
83
|
+
record directive-data {
|
|
84
|
+
name: string,
|
|
85
|
+
args: list<argument-info>,
|
|
86
|
+
line: u32,
|
|
87
|
+
column: u32,
|
|
88
|
+
start-offset: u32,
|
|
89
|
+
end-offset: u32,
|
|
90
|
+
end-line: u32,
|
|
91
|
+
end-column: u32,
|
|
92
|
+
leading-whitespace: string,
|
|
93
|
+
trailing-whitespace: string,
|
|
94
|
+
space-before-terminator: string,
|
|
95
|
+
has-block: bool,
|
|
96
|
+
block-is-raw: bool,
|
|
97
|
+
/// Actual raw content for raw blocks (e.g., lua code); none if not a raw block
|
|
98
|
+
block-raw-content: option<string>,
|
|
99
|
+
/// Leading whitespace before the closing brace; none if no block
|
|
100
|
+
closing-brace-leading-whitespace: option<string>,
|
|
101
|
+
/// Trailing whitespace after the closing brace; none if no block
|
|
102
|
+
block-trailing-whitespace: option<string>,
|
|
103
|
+
/// Text of the trailing comment on the directive line; none if no comment
|
|
104
|
+
trailing-comment-text: option<string>,
|
|
105
|
+
/// End column of the name token (1-based)
|
|
106
|
+
name-end-column: u32,
|
|
107
|
+
/// End byte offset of the name token (0-based)
|
|
108
|
+
name-end-offset: u32,
|
|
109
|
+
/// Start line of the block's opening brace (1-based); none if no block
|
|
110
|
+
block-start-line: option<u32>,
|
|
111
|
+
/// Start column of the block's opening brace (1-based); none if no block
|
|
112
|
+
block-start-column: option<u32>,
|
|
113
|
+
/// Start byte offset of the block's opening brace (0-based); none if no block
|
|
114
|
+
block-start-offset: option<u32>,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
interface config-api {
|
|
119
|
+
use types.{fix};
|
|
120
|
+
use data-types.{argument-type, argument-info, comment-info, blank-line-info, directive-data};
|
|
121
|
+
|
|
122
|
+
/// A config item (directive, comment, or blank line)
|
|
123
|
+
variant config-item {
|
|
124
|
+
directive-item(directive),
|
|
125
|
+
comment-item(comment-info),
|
|
126
|
+
blank-line-item(blank-line-info),
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// A directive paired with its parent block context
|
|
130
|
+
record directive-context {
|
|
131
|
+
directive: directive,
|
|
132
|
+
parent-stack: list<string>,
|
|
133
|
+
depth: u32,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// An nginx directive (resource backed by host data)
|
|
137
|
+
resource directive {
|
|
138
|
+
/// Get all flat properties in a single call (optimized for reconstruction)
|
|
139
|
+
data: func() -> directive-data;
|
|
140
|
+
/// Get the directive name
|
|
141
|
+
name: func() -> string;
|
|
142
|
+
/// Check if the directive has the given name
|
|
143
|
+
is: func(name: string) -> bool;
|
|
144
|
+
|
|
145
|
+
/// Get the first argument value
|
|
146
|
+
first-arg: func() -> option<string>;
|
|
147
|
+
/// Check if the first argument equals the given value
|
|
148
|
+
first-arg-is: func(value: string) -> bool;
|
|
149
|
+
/// Get the argument at the given index
|
|
150
|
+
arg-at: func(index: u32) -> option<string>;
|
|
151
|
+
/// Get the last argument value
|
|
152
|
+
last-arg: func() -> option<string>;
|
|
153
|
+
/// Check if any argument equals the given value
|
|
154
|
+
has-arg: func(value: string) -> bool;
|
|
155
|
+
/// Return the number of arguments
|
|
156
|
+
arg-count: func() -> u32;
|
|
157
|
+
/// Get all arguments as records
|
|
158
|
+
args: func() -> list<argument-info>;
|
|
159
|
+
|
|
160
|
+
/// Get the start line number (1-based)
|
|
161
|
+
line: func() -> u32;
|
|
162
|
+
/// Get the start column number (1-based)
|
|
163
|
+
column: func() -> u32;
|
|
164
|
+
/// Get the start byte offset (0-based)
|
|
165
|
+
start-offset: func() -> u32;
|
|
166
|
+
/// Get the end byte offset (0-based)
|
|
167
|
+
end-offset: func() -> u32;
|
|
168
|
+
/// Get the leading whitespace before the directive
|
|
169
|
+
leading-whitespace: func() -> string;
|
|
170
|
+
/// Get the trailing whitespace after the directive
|
|
171
|
+
trailing-whitespace: func() -> string;
|
|
172
|
+
/// Get the space before the terminator (; or {)
|
|
173
|
+
space-before-terminator: func() -> string;
|
|
174
|
+
|
|
175
|
+
/// Check if the directive has a block
|
|
176
|
+
has-block: func() -> bool;
|
|
177
|
+
/// Get the items inside the directive's block
|
|
178
|
+
block-items: func() -> list<config-item>;
|
|
179
|
+
/// Check if the block contains raw content (e.g., lua blocks)
|
|
180
|
+
block-is-raw: func() -> bool;
|
|
181
|
+
|
|
182
|
+
/// Create a fix that replaces this directive with new text
|
|
183
|
+
replace-with: func(new-text: string) -> fix;
|
|
184
|
+
/// Create a fix that deletes this directive's line
|
|
185
|
+
delete-line-fix: func() -> fix;
|
|
186
|
+
/// Create a fix that inserts a new line after this directive
|
|
187
|
+
insert-after: func(new-text: string) -> fix;
|
|
188
|
+
/// Create a fix that inserts a new line before this directive
|
|
189
|
+
insert-before: func(new-text: string) -> fix;
|
|
190
|
+
/// Create a fix that inserts multiple lines after this directive
|
|
191
|
+
insert-after-many: func(lines: list<string>) -> fix;
|
|
192
|
+
/// Create a fix that inserts multiple lines before this directive
|
|
193
|
+
insert-before-many: func(lines: list<string>) -> fix;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/// The parsed nginx configuration (resource backed by host data)
|
|
197
|
+
resource config {
|
|
198
|
+
/// Iterate over all directives recursively with parent context
|
|
199
|
+
all-directives-with-context: func() -> list<directive-context>;
|
|
200
|
+
/// Iterate over all directives recursively
|
|
201
|
+
all-directives: func() -> list<directive>;
|
|
202
|
+
/// Get the top-level config items
|
|
203
|
+
items: func() -> list<config-item>;
|
|
204
|
+
|
|
205
|
+
/// Get the include context (parent block names from include directive)
|
|
206
|
+
include-context: func() -> list<string>;
|
|
207
|
+
/// Check if this config is included from within a specific context
|
|
208
|
+
is-included-from: func(context: string) -> bool;
|
|
209
|
+
/// Check if included from http context
|
|
210
|
+
is-included-from-http: func() -> bool;
|
|
211
|
+
/// Check if included from http > server context
|
|
212
|
+
is-included-from-http-server: func() -> bool;
|
|
213
|
+
/// Check if included from http > ... > location context
|
|
214
|
+
is-included-from-http-location: func() -> bool;
|
|
215
|
+
/// Check if included from stream context
|
|
216
|
+
is-included-from-stream: func() -> bool;
|
|
217
|
+
/// Get the immediate parent context
|
|
218
|
+
immediate-parent-context: func() -> option<string>;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/// Record-based types for parser output (no resources, no recursion).
|
|
223
|
+
/// Uses index-based references to represent the tree structure:
|
|
224
|
+
/// all config items are stored in a flat array, with child-indices
|
|
225
|
+
/// pointing to children within that array.
|
|
226
|
+
interface parser-types {
|
|
227
|
+
use data-types.{comment-info, blank-line-info, directive-data};
|
|
228
|
+
|
|
229
|
+
/// The kind of a config item (non-recursive)
|
|
230
|
+
variant config-item-value {
|
|
231
|
+
directive-item(directive-data),
|
|
232
|
+
comment-item(comment-info),
|
|
233
|
+
blank-line-item(blank-line-info),
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/// A config item in the flat all-items array.
|
|
237
|
+
/// For directive items with blocks, child-indices contains the indices
|
|
238
|
+
/// of child items within the all-items array.
|
|
239
|
+
record config-item {
|
|
240
|
+
value: config-item-value,
|
|
241
|
+
child-indices: list<u32>,
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/// A directive paired with its parent block context
|
|
245
|
+
record directive-context {
|
|
246
|
+
data: directive-data,
|
|
247
|
+
/// Indices of block items in the all-items array
|
|
248
|
+
block-item-indices: list<u32>,
|
|
249
|
+
parent-stack: list<string>,
|
|
250
|
+
depth: u32,
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/// Complete parser output
|
|
254
|
+
record parse-output {
|
|
255
|
+
directives-with-context: list<directive-context>,
|
|
256
|
+
include-context: list<string>,
|
|
257
|
+
/// Flat array of all config items (DFS order)
|
|
258
|
+
all-items: list<config-item>,
|
|
259
|
+
/// Indices of top-level items in all-items
|
|
260
|
+
top-level-indices: list<u32>,
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
world plugin {
|
|
265
|
+
use types.{plugin-spec, lint-error};
|
|
266
|
+
use config-api.{config};
|
|
267
|
+
import config-api;
|
|
268
|
+
|
|
269
|
+
/// Return plugin metadata
|
|
270
|
+
export spec: func() -> plugin-spec;
|
|
271
|
+
|
|
272
|
+
/// Check config and return lint errors
|
|
273
|
+
export check: func(cfg: borrow<config>, path: string) -> list<lint-error>;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
world parser {
|
|
277
|
+
use parser-types.{parse-output};
|
|
278
|
+
|
|
279
|
+
/// Parse nginx config source and return structured output
|
|
280
|
+
export parse-config: func(source: string, include-context: list<string>) -> result<parse-output, string>;
|
|
281
|
+
}
|