@lightdash/cli 0.2192.0 → 0.2193.1
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/dist/.tsbuildinfo +1 -1
- package/dist/ajv.d.ts.map +1 -1
- package/dist/ajv.js +6 -1
- package/dist/handlers/lint/ajvToSarif.d.ts +66 -0
- package/dist/handlers/lint/ajvToSarif.d.ts.map +1 -0
- package/dist/handlers/lint/ajvToSarif.js +222 -0
- package/dist/handlers/lint/sarifFormatter.d.ts +14 -0
- package/dist/handlers/lint/sarifFormatter.d.ts.map +1 -0
- package/dist/handlers/lint/sarifFormatter.js +111 -0
- package/dist/handlers/lint.d.ts +8 -0
- package/dist/handlers/lint.d.ts.map +1 -0
- package/dist/handlers/lint.js +250 -0
- package/dist/index.js +16 -0
- package/package.json +4 -3
package/dist/ajv.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ajv.d.ts","sourceRoot":"","sources":["../src/ajv.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"ajv.d.ts","sourceRoot":"","sources":["../src/ajv.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AAGtB,eAAO,MAAM,GAAG,KAGd,CAAC"}
|
package/dist/ajv.js
CHANGED
|
@@ -3,4 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ajv = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const ajv_1 = tslib_1.__importDefault(require("ajv"));
|
|
6
|
-
|
|
6
|
+
const ajv_formats_1 = tslib_1.__importDefault(require("ajv-formats"));
|
|
7
|
+
exports.ajv = new ajv_1.default({
|
|
8
|
+
coerceTypes: true,
|
|
9
|
+
allErrors: true, // Report all errors, not just the first one
|
|
10
|
+
});
|
|
11
|
+
(0, ajv_formats_1.default)(exports.ajv);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ErrorObject } from 'ajv';
|
|
2
|
+
type LocationMap = Map<string, {
|
|
3
|
+
line: number;
|
|
4
|
+
column: number;
|
|
5
|
+
}>;
|
|
6
|
+
type FileValidationResult = {
|
|
7
|
+
filePath: string;
|
|
8
|
+
errors: ErrorObject[];
|
|
9
|
+
fileContent: string;
|
|
10
|
+
locationMap?: LocationMap;
|
|
11
|
+
schemaType: 'chart' | 'dashboard';
|
|
12
|
+
};
|
|
13
|
+
export type SarifLog = {
|
|
14
|
+
version: '2.1.0';
|
|
15
|
+
$schema: string;
|
|
16
|
+
runs: SarifRun[];
|
|
17
|
+
};
|
|
18
|
+
type SarifRun = {
|
|
19
|
+
tool: {
|
|
20
|
+
driver: {
|
|
21
|
+
name: string;
|
|
22
|
+
version: string;
|
|
23
|
+
informationUri?: string;
|
|
24
|
+
rules?: SarifRule[];
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
results: SarifResult[];
|
|
28
|
+
};
|
|
29
|
+
type SarifRule = {
|
|
30
|
+
id: string;
|
|
31
|
+
shortDescription: {
|
|
32
|
+
text: string;
|
|
33
|
+
};
|
|
34
|
+
fullDescription?: {
|
|
35
|
+
text: string;
|
|
36
|
+
};
|
|
37
|
+
help?: {
|
|
38
|
+
text: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
export type SarifResult = {
|
|
42
|
+
ruleId: string;
|
|
43
|
+
level: 'error' | 'warning' | 'note';
|
|
44
|
+
message: {
|
|
45
|
+
text: string;
|
|
46
|
+
};
|
|
47
|
+
locations: SarifLocation[];
|
|
48
|
+
properties?: Record<string, unknown>;
|
|
49
|
+
};
|
|
50
|
+
type SarifLocation = {
|
|
51
|
+
physicalLocation: {
|
|
52
|
+
artifactLocation: {
|
|
53
|
+
uri: string;
|
|
54
|
+
};
|
|
55
|
+
region: {
|
|
56
|
+
startLine: number;
|
|
57
|
+
startColumn: number;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Convert validation results to SARIF format
|
|
63
|
+
*/
|
|
64
|
+
export declare function createSarifReport(results: FileValidationResult[]): SarifLog;
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=ajvToSarif.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ajvToSarif.d.ts","sourceRoot":"","sources":["../../../src/handlers/lint/ajvToSarif.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAEvC,KAAK,WAAW,GAAG,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjE,KAAK,oBAAoB,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,UAAU,EAAE,OAAO,GAAG,WAAW,CAAC;CACrC,CAAC;AAGF,MAAM,MAAM,QAAQ,GAAG;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,QAAQ,EAAE,CAAC;CACpB,CAAC;AAEF,KAAK,QAAQ,GAAG;IACZ,IAAI,EAAE;QACF,MAAM,EAAE;YACJ,IAAI,EAAE,MAAM,CAAC;YACb,OAAO,EAAE,MAAM,CAAC;YAChB,cAAc,CAAC,EAAE,MAAM,CAAC;YACxB,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC;SACvB,CAAC;KACL,CAAC;IACF,OAAO,EAAE,WAAW,EAAE,CAAC;CAC1B,CAAC;AAEF,KAAK,SAAS,GAAG;IACb,EAAE,EAAE,MAAM,CAAC;IACX,gBAAgB,EAAE;QACd,IAAI,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,eAAe,CAAC,EAAE;QACd,IAAI,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,IAAI,CAAC,EAAE;QACH,IAAI,EAAE,MAAM,CAAC;KAChB,CAAC;CACL,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACpC,OAAO,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC,CAAC;AAEF,KAAK,aAAa,GAAG;IACjB,gBAAgB,EAAE;QACd,gBAAgB,EAAE;YACd,GAAG,EAAE,MAAM,CAAC;SACf,CAAC;QACF,MAAM,EAAE;YACJ,SAAS,EAAE,MAAM,CAAC;YAClB,WAAW,EAAE,MAAM,CAAC;SACvB,CAAC;KACL,CAAC;CACL,CAAC;AAmJF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,QAAQ,CAwG3E"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSarifReport = createSarifReport;
|
|
4
|
+
/**
|
|
5
|
+
* Find the line and column number in YAML content for a given data path using regex-based search.
|
|
6
|
+
*
|
|
7
|
+
* This is a FALLBACK strategy used when the locationMap doesn't have an entry for the error path.
|
|
8
|
+
* Primary use case: root-level missing required properties (dataPath='/' which isn't in locationMap).
|
|
9
|
+
*
|
|
10
|
+
* The function uses regex patterns to search through the YAML content and locate:
|
|
11
|
+
* - Array items by counting '-' markers
|
|
12
|
+
* - Nested properties within array items
|
|
13
|
+
* - Simple top-level keys
|
|
14
|
+
*
|
|
15
|
+
* Returns line 1, column 1 if no match is found (used for root-level errors).
|
|
16
|
+
*/
|
|
17
|
+
function findLocationForPath(yamlContent, dataPath) {
|
|
18
|
+
const lines = yamlContent.split('\n');
|
|
19
|
+
// Remove leading slash and split path
|
|
20
|
+
const pathParts = dataPath
|
|
21
|
+
.replace(/^\//, '')
|
|
22
|
+
.split('/')
|
|
23
|
+
.filter((p) => p !== '');
|
|
24
|
+
if (pathParts.length === 0) {
|
|
25
|
+
// Error at root level
|
|
26
|
+
return { line: 1, column: 1 };
|
|
27
|
+
}
|
|
28
|
+
// Check if we have an array index in the path (e.g., /dimensions/1/type)
|
|
29
|
+
let arrayParentKey = null;
|
|
30
|
+
let arrayIndex = null;
|
|
31
|
+
let propertyInArray = null;
|
|
32
|
+
// Look for pattern: parent/index/property
|
|
33
|
+
if (pathParts.length >= 3) {
|
|
34
|
+
const secondToLast = pathParts[pathParts.length - 2];
|
|
35
|
+
if (/^\d+$/.test(secondToLast)) {
|
|
36
|
+
// We have an array index
|
|
37
|
+
arrayParentKey = pathParts[pathParts.length - 3];
|
|
38
|
+
arrayIndex = parseInt(secondToLast, 10);
|
|
39
|
+
propertyInArray = pathParts[pathParts.length - 1];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (pathParts.length === 2) {
|
|
43
|
+
const [firstPart, lastPart] = pathParts;
|
|
44
|
+
if (/^\d+$/.test(lastPart)) {
|
|
45
|
+
// Path is like /dimensions/1
|
|
46
|
+
arrayParentKey = firstPart;
|
|
47
|
+
arrayIndex = parseInt(lastPart, 10);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// For array items with nested property errors (e.g., /dimensions/1/type)
|
|
51
|
+
if (arrayParentKey && arrayIndex !== null && propertyInArray) {
|
|
52
|
+
const parentPattern = new RegExp(`^\\s*${arrayParentKey}\\s*:`);
|
|
53
|
+
let foundParent = false;
|
|
54
|
+
let arrayItemCount = 0;
|
|
55
|
+
let arrayItemStartLine = -1;
|
|
56
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
57
|
+
if (!foundParent && parentPattern.test(lines[i])) {
|
|
58
|
+
foundParent = true;
|
|
59
|
+
}
|
|
60
|
+
else if (foundParent && /^\s*-\s/.test(lines[i])) {
|
|
61
|
+
if (arrayItemCount === arrayIndex) {
|
|
62
|
+
arrayItemStartLine = i;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
arrayItemCount += 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Now find the nested property within this array item
|
|
69
|
+
if (arrayItemStartLine >= 0) {
|
|
70
|
+
// Check if property is on the same line as the array marker (e.g., "- id: value")
|
|
71
|
+
const sameLinePattern = new RegExp(`^\\s*-\\s+${propertyInArray}\\s*:`);
|
|
72
|
+
if (sameLinePattern.test(lines[arrayItemStartLine])) {
|
|
73
|
+
const match = lines[arrayItemStartLine].match(/^(\s*-\s+)/);
|
|
74
|
+
const column = match ? match[1].length + 1 : 1;
|
|
75
|
+
return { line: arrayItemStartLine + 1, column };
|
|
76
|
+
}
|
|
77
|
+
// Otherwise, look for the property on subsequent lines
|
|
78
|
+
const propPattern = new RegExp(`^\\s*${propertyInArray}\\s*:`);
|
|
79
|
+
for (let i = arrayItemStartLine + 1; i < Math.min(lines.length, arrayItemStartLine + 20); i += 1) {
|
|
80
|
+
if (propPattern.test(lines[i])) {
|
|
81
|
+
const match = lines[i].match(/^(\s*)/);
|
|
82
|
+
const column = match ? match[1].length + 1 : 1;
|
|
83
|
+
return { line: i + 1, column };
|
|
84
|
+
}
|
|
85
|
+
if (/^\s*-\s/.test(lines[i])) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Simple search for the last key in the path
|
|
92
|
+
const lastPart = pathParts[pathParts.length - 1];
|
|
93
|
+
if (!/^\d+$/.test(lastPart)) {
|
|
94
|
+
const keyPattern = new RegExp(`^\\s*${lastPart}\\s*:`);
|
|
95
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
96
|
+
if (keyPattern.test(lines[i])) {
|
|
97
|
+
const match = lines[i].match(/^(\s*)/);
|
|
98
|
+
const column = match ? match[1].length + 1 : 1;
|
|
99
|
+
return { line: i + 1, column };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { line: 1, column: 1 };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get a friendly error message for an AJV error
|
|
107
|
+
*/
|
|
108
|
+
function getFriendlyMessage(error) {
|
|
109
|
+
if (error.keyword === 'required' && error.params.missingProperty) {
|
|
110
|
+
return `Missing required property '${error.params.missingProperty}'`;
|
|
111
|
+
}
|
|
112
|
+
if (error.keyword === 'additionalProperties' &&
|
|
113
|
+
error.params.additionalProperty) {
|
|
114
|
+
return `Property '${error.params.additionalProperty}' is not allowed`;
|
|
115
|
+
}
|
|
116
|
+
if (error.keyword === 'type') {
|
|
117
|
+
return `Expected type '${error.params.type}'`;
|
|
118
|
+
}
|
|
119
|
+
if (error.keyword === 'enum' && error.params.allowedValues) {
|
|
120
|
+
return `Value must be one of: ${error.params.allowedValues.join(', ')}`;
|
|
121
|
+
}
|
|
122
|
+
if (error.keyword === 'const') {
|
|
123
|
+
return `Value must be '${error.params.allowedValue}'`;
|
|
124
|
+
}
|
|
125
|
+
return error.message || 'Validation error';
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Convert validation results to SARIF format
|
|
129
|
+
*/
|
|
130
|
+
function createSarifReport(results) {
|
|
131
|
+
const sarifResults = [];
|
|
132
|
+
const rules = new Map();
|
|
133
|
+
for (const result of results) {
|
|
134
|
+
for (const error of result.errors) {
|
|
135
|
+
// For additionalProperties errors, append the property name to the path
|
|
136
|
+
// Handle root-level errors carefully to avoid double slashes (//propertyName)
|
|
137
|
+
let dataPath = error.instancePath || '/';
|
|
138
|
+
if (error.keyword === 'additionalProperties' &&
|
|
139
|
+
error.params.additionalProperty) {
|
|
140
|
+
dataPath =
|
|
141
|
+
dataPath === '/'
|
|
142
|
+
? `/${error.params.additionalProperty}`
|
|
143
|
+
: `${dataPath}/${error.params.additionalProperty}`;
|
|
144
|
+
}
|
|
145
|
+
// Determine error location using a two-strategy approach:
|
|
146
|
+
// 1. PRIMARY: Use locationMap (built from YAML AST during parsing)
|
|
147
|
+
// - Fast O(1) lookup for ~95% of cases
|
|
148
|
+
// - Works for: additional properties, type errors, enum errors, nested errors, array items
|
|
149
|
+
// - Works for: nested missing required fields (e.g., missing 'exploreName' in 'metricQuery')
|
|
150
|
+
//
|
|
151
|
+
// 2. FALLBACK: Use regex-based search when locationMap lookup fails
|
|
152
|
+
// - Needed for: root-level missing required properties (e.g., missing 'name', 'version')
|
|
153
|
+
// These have dataPath='/' which doesn't exist in locationMap since it stores actual YAML keys
|
|
154
|
+
// - Also provides defensive error handling if AST traversal misses any edge cases
|
|
155
|
+
let location = null;
|
|
156
|
+
if (result.locationMap) {
|
|
157
|
+
location = result.locationMap.get(dataPath) || null;
|
|
158
|
+
}
|
|
159
|
+
if (!location) {
|
|
160
|
+
// Fallback to regex search - primarily for root-level missing required properties
|
|
161
|
+
location = findLocationForPath(result.fileContent, dataPath);
|
|
162
|
+
}
|
|
163
|
+
const message = getFriendlyMessage(error);
|
|
164
|
+
const ruleId = `${result.schemaType}/${error.keyword}`;
|
|
165
|
+
// Add rule if we haven't seen it before
|
|
166
|
+
if (!rules.has(ruleId)) {
|
|
167
|
+
rules.set(ruleId, {
|
|
168
|
+
id: ruleId,
|
|
169
|
+
shortDescription: {
|
|
170
|
+
text: `${error.keyword} validation error`,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
const sarifResult = {
|
|
175
|
+
ruleId,
|
|
176
|
+
level: 'error',
|
|
177
|
+
message: {
|
|
178
|
+
text: message,
|
|
179
|
+
},
|
|
180
|
+
locations: [
|
|
181
|
+
{
|
|
182
|
+
physicalLocation: {
|
|
183
|
+
artifactLocation: {
|
|
184
|
+
uri: result.filePath,
|
|
185
|
+
},
|
|
186
|
+
region: {
|
|
187
|
+
startLine: location?.line || 1,
|
|
188
|
+
startColumn: location?.column || 1,
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
};
|
|
194
|
+
// Add additional context
|
|
195
|
+
if (error.params) {
|
|
196
|
+
const properties = {};
|
|
197
|
+
Object.entries(error.params).forEach(([key, value]) => {
|
|
198
|
+
properties[key] = value;
|
|
199
|
+
});
|
|
200
|
+
sarifResult.properties = { errorParams: properties };
|
|
201
|
+
}
|
|
202
|
+
sarifResults.push(sarifResult);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
version: '2.1.0',
|
|
207
|
+
$schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
|
|
208
|
+
runs: [
|
|
209
|
+
{
|
|
210
|
+
tool: {
|
|
211
|
+
driver: {
|
|
212
|
+
name: 'lightdash-lint',
|
|
213
|
+
version: '1.0.0',
|
|
214
|
+
informationUri: 'https://github.com/lightdash/lightdash',
|
|
215
|
+
rules: Array.from(rules.values()),
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
results: sarifResults,
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
};
|
|
222
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SarifLog } from './ajvToSarif';
|
|
2
|
+
/**
|
|
3
|
+
* Format SARIF results for CLI output
|
|
4
|
+
*/
|
|
5
|
+
export declare function formatSarifForCli(sarifLog: SarifLog, searchPath: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Get summary statistics from SARIF log
|
|
8
|
+
*/
|
|
9
|
+
export declare function getSarifSummary(sarifLog: SarifLog): {
|
|
10
|
+
totalFiles: number;
|
|
11
|
+
totalErrors: number;
|
|
12
|
+
hasErrors: boolean;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=sarifFormatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sarifFormatter.d.ts","sourceRoot":"","sources":["../../../src/handlers/lint/sarifFormatter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,cAAc,CAAC;AAgC1D;;GAEG;AACH,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,GACnB,MAAM,CA2ER;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;CACtB,CAsBA"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatSarifForCli = formatSarifForCli;
|
|
4
|
+
exports.getSarifSummary = getSarifSummary;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
8
|
+
const path = tslib_1.__importStar(require("path"));
|
|
9
|
+
/**
|
|
10
|
+
* Get a snippet of the file around the error location
|
|
11
|
+
*/
|
|
12
|
+
function getCodeSnippet(filePath, line, contextLines = 2) {
|
|
13
|
+
try {
|
|
14
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
15
|
+
const lines = content.split('\n');
|
|
16
|
+
const startLine = Math.max(0, line - 1 - contextLines);
|
|
17
|
+
const endLine = Math.min(lines.length, line + contextLines);
|
|
18
|
+
return lines
|
|
19
|
+
.slice(startLine, endLine)
|
|
20
|
+
.map((lineText, idx) => {
|
|
21
|
+
const lineNum = startLine + idx + 1;
|
|
22
|
+
const prefix = lineNum === line ? '→ ' : ' ';
|
|
23
|
+
return `${prefix}${lineNum
|
|
24
|
+
.toString()
|
|
25
|
+
.padStart(3, ' ')} | ${lineText}`;
|
|
26
|
+
})
|
|
27
|
+
.join('\n');
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Format SARIF results for CLI output
|
|
35
|
+
*/
|
|
36
|
+
function formatSarifForCli(sarifLog, searchPath) {
|
|
37
|
+
const output = [];
|
|
38
|
+
if (!sarifLog.runs || sarifLog.runs.length === 0) {
|
|
39
|
+
return chalk_1.default.green('\n✓ All Lightdash Code files are valid!\n');
|
|
40
|
+
}
|
|
41
|
+
const run = sarifLog.runs[0];
|
|
42
|
+
const results = run.results || [];
|
|
43
|
+
if (results.length === 0) {
|
|
44
|
+
return chalk_1.default.green('\n✓ All Lightdash Code files are valid!\n');
|
|
45
|
+
}
|
|
46
|
+
// Group results by file
|
|
47
|
+
const resultsByFile = new Map();
|
|
48
|
+
for (const result of results) {
|
|
49
|
+
const location = result.locations?.[0];
|
|
50
|
+
const uri = location?.physicalLocation?.artifactLocation?.uri;
|
|
51
|
+
if (uri) {
|
|
52
|
+
if (!resultsByFile.has(uri)) {
|
|
53
|
+
resultsByFile.set(uri, []);
|
|
54
|
+
}
|
|
55
|
+
resultsByFile.get(uri).push(result);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Count stats
|
|
59
|
+
const totalFiles = resultsByFile.size;
|
|
60
|
+
output.push(chalk_1.default.red('\nValidation Errors:\n'));
|
|
61
|
+
// Format each file's errors
|
|
62
|
+
for (const [fileUri, fileResults] of resultsByFile.entries()) {
|
|
63
|
+
const relativePath = path.relative(searchPath, fileUri);
|
|
64
|
+
output.push(chalk_1.default.red.bold(`\n✗ ${relativePath}`));
|
|
65
|
+
for (const result of fileResults) {
|
|
66
|
+
const message = result.message.text;
|
|
67
|
+
const location = result.locations?.[0]?.physicalLocation;
|
|
68
|
+
const line = location?.region?.startLine;
|
|
69
|
+
const column = location?.region?.startColumn;
|
|
70
|
+
output.push(chalk_1.default.red(`\n ${message}${line ? ` (line ${line})` : ''}`));
|
|
71
|
+
// Add clickable file link for IDEs
|
|
72
|
+
if (line) {
|
|
73
|
+
// Encode the URI properly for file:// protocol to handle special characters (#, ?, &, spaces, etc.)
|
|
74
|
+
const encodedPath = encodeURI(fileUri);
|
|
75
|
+
const fileLink = `file://${encodedPath}:${line}${column ? `:${column}` : ''}`;
|
|
76
|
+
output.push(chalk_1.default.dim(` ${fileLink}`));
|
|
77
|
+
}
|
|
78
|
+
if (line && fs.existsSync(fileUri)) {
|
|
79
|
+
const snippet = getCodeSnippet(fileUri, line);
|
|
80
|
+
if (snippet) {
|
|
81
|
+
output.push(chalk_1.default.dim(`\n${snippet}\n`));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
output.push(chalk_1.default.red(`\nValidation failed for ${totalFiles} file${totalFiles > 1 ? 's' : ''}`));
|
|
87
|
+
return output.join('\n');
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get summary statistics from SARIF log
|
|
91
|
+
*/
|
|
92
|
+
function getSarifSummary(sarifLog) {
|
|
93
|
+
if (!sarifLog.runs || sarifLog.runs.length === 0) {
|
|
94
|
+
return { totalFiles: 0, totalErrors: 0, hasErrors: false };
|
|
95
|
+
}
|
|
96
|
+
const run = sarifLog.runs[0];
|
|
97
|
+
const results = run.results || [];
|
|
98
|
+
const uniqueFiles = new Set();
|
|
99
|
+
for (const result of results) {
|
|
100
|
+
const location = result.locations?.[0];
|
|
101
|
+
const uri = location?.physicalLocation?.artifactLocation?.uri;
|
|
102
|
+
if (uri) {
|
|
103
|
+
uniqueFiles.add(uri);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
totalFiles: uniqueFiles.size,
|
|
108
|
+
totalErrors: results.length,
|
|
109
|
+
hasErrors: results.length > 0,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/handlers/lint.ts"],"names":[],"mappings":"AAUA,KAAK,WAAW,GAAG;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CAC3B,CAAC;AAgNF,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAmGrE"}
|