0
# Code Validation
1
2
The Validator plugin validates code for errors, style issues, and other quality concerns during the build process. Validators can run linting, type checking, and other code quality analysis.
3
4
## Capabilities
5
6
### Validator Class
7
8
Base class for creating code validation plugins.
9
10
```typescript { .api }
11
/**
12
* Base class for code validation plugins
13
* @template T - Configuration type for this validator
14
*/
15
export declare class Validator<T> {
16
constructor(opts: ValidatorOpts);
17
}
18
19
/**
20
* Validator plugin configuration (union of two validator types)
21
*/
22
type ValidatorOpts = DedicatedThreadValidator | MultiThreadValidator;
23
24
/**
25
* Validator that processes all assets at once in a dedicated thread
26
*/
27
interface DedicatedThreadValidator {
28
/** Validate all assets together */
29
validateAll(args: {
30
assets: Asset[];
31
resolveConfigWithPath: ResolveConfigWithPathFn;
32
options: PluginOptions;
33
logger: PluginLogger;
34
tracer: PluginTracer;
35
}): Promise<Array<ValidateResult | null>>;
36
}
37
38
/**
39
* Validator that processes assets individually across multiple threads
40
*/
41
interface MultiThreadValidator {
42
/** Get configuration for an asset */
43
getConfig?(args: {
44
asset: Asset;
45
resolveConfig: ResolveConfigFn;
46
options: PluginOptions;
47
logger: PluginLogger;
48
tracer: PluginTracer;
49
}): Promise<ConfigResult>;
50
51
/** Validate a single asset */
52
validate(args: {
53
asset: Asset;
54
config: ConfigResult | void;
55
options: PluginOptions;
56
logger: PluginLogger;
57
tracer: PluginTracer;
58
}): Promise<ValidateResult | void>;
59
}
60
```
61
62
### Validation Results
63
64
```typescript { .api }
65
/**
66
* Result from validating an asset
67
*/
68
interface ValidateResult {
69
/** Validation diagnostics (errors, warnings) */
70
diagnostics: Array<Diagnostic>;
71
72
/** Whether validation passed */
73
isValid: boolean;
74
75
/** Additional metadata */
76
meta?: Record<string, any>;
77
}
78
79
/**
80
* Configuration loading result
81
*/
82
interface ConfigResult {
83
/** Configuration object */
84
config: any;
85
86
/** Files that were read to load this config */
87
files: Array<FilePath>;
88
}
89
90
/**
91
* Function to resolve configuration with path information
92
*/
93
type ResolveConfigWithPathFn = (
94
configNames: Array<string>,
95
fromPath: FilePath
96
) => Promise<ConfigResult | null>;
97
98
/**
99
* Function to resolve configuration
100
*/
101
type ResolveConfigFn = (
102
configNames: Array<string>
103
) => Promise<any>;
104
```
105
106
**Single Asset Validator Example:**
107
108
```javascript
109
import { Validator } from "@parcel/plugin";
110
import eslint from "eslint";
111
112
export default new Validator({
113
// Get configuration for validation
114
async getConfig({asset, resolveConfig}) {
115
// Only validate JavaScript files
116
if (!asset.filePath.endsWith('.js')) {
117
return null;
118
}
119
120
// Load ESLint configuration
121
const config = await resolveConfig([
122
'.eslintrc.js',
123
'.eslintrc.json',
124
'.eslintrc'
125
]);
126
127
return { config, files: [] };
128
},
129
130
// Validate single asset
131
async validate({asset, config, logger}) {
132
if (!config) {
133
return null; // Skip validation if no config
134
}
135
136
const code = await asset.getCode();
137
const linter = new eslint.ESLint({
138
baseConfig: config.config,
139
useEslintrc: false
140
});
141
142
try {
143
const results = await linter.lintText(code, {
144
filePath: asset.filePath
145
});
146
147
const diagnostics = [];
148
149
for (const result of results) {
150
for (const message of result.messages) {
151
diagnostics.push({
152
level: message.severity === 2 ? 'error' : 'warning',
153
message: message.message,
154
filePath: asset.filePath,
155
start: {
156
line: message.line,
157
column: message.column
158
},
159
hints: message.fix ? ['Run ESLint with --fix to auto-correct this issue'] : undefined
160
});
161
}
162
}
163
164
return {
165
diagnostics,
166
isValid: diagnostics.every(d => d.level !== 'error')
167
};
168
169
} catch (error) {
170
return {
171
diagnostics: [{
172
level: 'error',
173
message: `ESLint validation failed: ${error.message}`,
174
filePath: asset.filePath
175
}],
176
isValid: false
177
};
178
}
179
}
180
});
181
```
182
183
**Batch Validator Example:**
184
185
```javascript
186
import { Validator } from "@parcel/plugin";
187
import typescript from "typescript";
188
189
export default new Validator({
190
// Validate all TypeScript assets together
191
async validateAll({assets, resolveConfigWithPath, options, logger}) {
192
// Filter TypeScript assets
193
const tsAssets = assets.filter(asset =>
194
asset.filePath.endsWith('.ts') || asset.filePath.endsWith('.tsx')
195
);
196
197
if (tsAssets.length === 0) {
198
return [];
199
}
200
201
// Load TypeScript configuration
202
const configResult = await resolveConfigWithPath([
203
'tsconfig.json'
204
], options.projectRoot);
205
206
const tsConfig = configResult?.config || {};
207
208
// Create TypeScript program
209
const fileNames = tsAssets.map(asset => asset.filePath);
210
const program = typescript.createProgram(fileNames, tsConfig.compilerOptions || {});
211
212
// Get diagnostics
213
const diagnostics = typescript.getPreEmitDiagnostics(program);
214
215
// Convert to validation results
216
const results = [];
217
218
for (const asset of tsAssets) {
219
const assetDiagnostics = diagnostics
220
.filter(d => d.file?.fileName === asset.filePath)
221
.map(d => this.convertTSDiagnostic(d));
222
223
results.push({
224
diagnostics: assetDiagnostics,
225
isValid: assetDiagnostics.every(d => d.level !== 'error')
226
});
227
}
228
229
return results;
230
},
231
232
convertTSDiagnostic(diagnostic) {
233
const message = typescript.flattenDiagnosticMessageText(
234
diagnostic.messageText,
235
'\n'
236
);
237
238
let start = undefined;
239
if (diagnostic.file && diagnostic.start != null) {
240
const pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
241
start = {
242
line: pos.line + 1,
243
column: pos.character
244
};
245
}
246
247
return {
248
level: diagnostic.category === typescript.DiagnosticCategory.Error ? 'error' : 'warning',
249
message,
250
filePath: diagnostic.file?.fileName,
251
start,
252
documentationURL: `https://typescript.tv/errors/#TS${diagnostic.code}`
253
};
254
}
255
});
256
```
257
258
### Common Validation Patterns
259
260
**File Type Filtering:**
261
```javascript
262
// Only validate specific file types
263
if (!asset.filePath.match(/\.(js|ts|jsx|tsx)$/)) {
264
return null;
265
}
266
```
267
268
**Configuration Loading:**
269
```javascript
270
// Load validation configuration
271
const config = await resolveConfig([
272
'.eslintrc.js',
273
'.eslintrc.json',
274
'package.json' // Look for eslintConfig key
275
]);
276
```
277
278
**Error Aggregation:**
279
```javascript
280
// Collect errors from multiple sources
281
const allDiagnostics = [
282
...syntaxErrors,
283
...lintingErrors,
284
...typeErrors
285
];
286
287
return {
288
diagnostics: allDiagnostics,
289
isValid: allDiagnostics.every(d => d.level !== 'error')
290
};
291
```