@kitajs/ts-html-plugin 4.1.4 → 5.0.0-next.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/README.md +64 -37
- package/bin/index.js +8 -0
- package/dist/cli.d.ts +1 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +9 -8
- package/dist/cli.js.map +1 -1
- package/dist/errors.d.ts +4 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +15 -18
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/util.d.ts +2 -4
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +101 -38
- package/dist/util.js.map +1 -1
- package/package.json +23 -15
- package/src/cli.ts +229 -0
- package/src/errors.ts +34 -0
- package/src/index.ts +43 -0
- package/src/util.ts +438 -0
- package/dist/tsconfig.build.tsbuildinfo +0 -1
package/dist/util.js
CHANGED
|
@@ -1,14 +1,44 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isJsx = isJsx;
|
|
4
36
|
exports.recursiveDiagnoseJsxElements = recursiveDiagnoseJsxElements;
|
|
5
37
|
exports.diagnoseJsxElement = diagnoseJsxElement;
|
|
6
38
|
exports.isSafeAttribute = isSafeAttribute;
|
|
7
39
|
exports.getSafeAttribute = getSafeAttribute;
|
|
8
40
|
exports.proxyObject = proxyObject;
|
|
9
|
-
const
|
|
10
|
-
const typescript_1 = tslib_1.__importDefault(require("typescript"));
|
|
11
|
-
const Errors = tslib_1.__importStar(require("./errors"));
|
|
41
|
+
const Errors = __importStar(require("./errors"));
|
|
12
42
|
const UPPERCASE = /[A-Z]/;
|
|
13
43
|
const ESCAPE_HTML_REGEX = /^(\w+\.)?(escapeHtml|e\s*`|escape)/i;
|
|
14
44
|
/** If the node is a JSX element or fragment */
|
|
@@ -35,9 +65,9 @@ function recursiveDiagnoseJsxElements(ts, node, typeChecker, original) {
|
|
|
35
65
|
}
|
|
36
66
|
}
|
|
37
67
|
}
|
|
38
|
-
function diagnostic(node, error, category) {
|
|
68
|
+
function diagnostic(ts, node, error, category) {
|
|
39
69
|
return {
|
|
40
|
-
category:
|
|
70
|
+
category: ts.DiagnosticCategory[category],
|
|
41
71
|
messageText: Errors[error].message,
|
|
42
72
|
code: Errors[error].code,
|
|
43
73
|
file: node.getSourceFile(),
|
|
@@ -60,16 +90,16 @@ function diagnoseJsxElement(ts, node, typeChecker, diagnostics) {
|
|
|
60
90
|
node.children.length === 0 ||
|
|
61
91
|
// Only text elements
|
|
62
92
|
(node.children.length === 1 && node.children[0].kind === ts.SyntaxKind.JsxText)) {
|
|
63
|
-
diagnostics.push(diagnostic(safeAttribute, 'UnusedSafe', 'Warning'));
|
|
93
|
+
diagnostics.push(diagnostic(ts, safeAttribute, 'UnusedSafe', 'Warning'));
|
|
64
94
|
return;
|
|
65
95
|
}
|
|
66
96
|
for (const exp of node.children) {
|
|
67
97
|
if (
|
|
68
|
-
// JSX Element inside safe
|
|
69
|
-
ts
|
|
98
|
+
// JSX Element inside safe (includes self-closing elements like <Component />)
|
|
99
|
+
isJsx(ts, exp) ||
|
|
70
100
|
// Element is using safe with escapeHtml
|
|
71
101
|
(ts.isJsxExpression(exp) && exp.expression?.getText().match(ESCAPE_HTML_REGEX))) {
|
|
72
|
-
diagnostics.push(diagnostic(safeAttribute, 'DoubleEscape', 'Error'));
|
|
102
|
+
diagnostics.push(diagnostic(ts, safeAttribute, 'DoubleEscape', 'Error'));
|
|
73
103
|
continue;
|
|
74
104
|
}
|
|
75
105
|
// Warn on unnecessary safe attributes
|
|
@@ -77,15 +107,15 @@ function diagnoseJsxElement(ts, node, typeChecker, diagnostics) {
|
|
|
77
107
|
// has inner expression
|
|
78
108
|
exp.expression) {
|
|
79
109
|
// gets this expression or array of sub expressions
|
|
80
|
-
const expressions = getNodeExpressions(exp.expression) || [exp.expression];
|
|
81
|
-
// at least one jsx inside another jsx with safe
|
|
82
|
-
if (expressions.some((inner) => ts
|
|
83
|
-
diagnostics.push(diagnostic(safeAttribute, 'DoubleEscape', 'Error'));
|
|
110
|
+
const expressions = getNodeExpressions(ts, exp.expression) || [exp.expression];
|
|
111
|
+
// at least one jsx inside another jsx with safe (includes self-closing elements)
|
|
112
|
+
if (expressions.some((inner) => isJsx(ts, inner))) {
|
|
113
|
+
diagnostics.push(diagnostic(ts, safeAttribute, 'DoubleEscape', 'Error'));
|
|
84
114
|
continue;
|
|
85
115
|
}
|
|
86
116
|
// all of them must be safe
|
|
87
117
|
if (expressions.every((inner) => isSafeAttribute(ts, typeChecker.getTypeAtLocation(inner), typeChecker, inner))) {
|
|
88
|
-
diagnostics.push(diagnostic(safeAttribute, 'UnusedSafe', 'Warning'));
|
|
118
|
+
diagnostics.push(diagnostic(ts, safeAttribute, 'UnusedSafe', 'Warning'));
|
|
89
119
|
}
|
|
90
120
|
}
|
|
91
121
|
}
|
|
@@ -94,7 +124,7 @@ function diagnoseJsxElement(ts, node, typeChecker, diagnostics) {
|
|
|
94
124
|
}
|
|
95
125
|
// If this expression does not have children, we can ignore it
|
|
96
126
|
// for example it could be a self closing element
|
|
97
|
-
if (!node.children) {
|
|
127
|
+
if (ts.isJsxSelfClosingElement(node) || !node.children) {
|
|
98
128
|
return;
|
|
99
129
|
}
|
|
100
130
|
// Look for expressions
|
|
@@ -119,7 +149,7 @@ function diagnoseExpression(ts, node, typeChecker, diagnostics, isComponent) {
|
|
|
119
149
|
if (isJsx(ts, node)) {
|
|
120
150
|
return;
|
|
121
151
|
}
|
|
122
|
-
const expressions = getNodeExpressions(node);
|
|
152
|
+
const expressions = getNodeExpressions(ts, node);
|
|
123
153
|
// ternary or binary expressions should be evaluated on each side
|
|
124
154
|
if (expressions) {
|
|
125
155
|
for (const inner of expressions) {
|
|
@@ -150,10 +180,10 @@ function diagnoseExpression(ts, node, typeChecker, diagnostics, isComponent) {
|
|
|
150
180
|
}
|
|
151
181
|
// Switch between component and element xss errors
|
|
152
182
|
if (isComponent || ts.isJsxFragment(node)) {
|
|
153
|
-
diagnostics.push(diagnostic(node, 'ComponentXss', 'Error'));
|
|
183
|
+
diagnostics.push(diagnostic(ts, node, 'ComponentXss', 'Error'));
|
|
154
184
|
}
|
|
155
185
|
else {
|
|
156
|
-
diagnostics.push(diagnostic(node, 'Xss', 'Error'));
|
|
186
|
+
diagnostics.push(diagnostic(ts, node, 'Xss', 'Error'));
|
|
157
187
|
}
|
|
158
188
|
}
|
|
159
189
|
function isSafeAttribute(ts, type, checker, node) {
|
|
@@ -161,12 +191,39 @@ function isSafeAttribute(ts, type, checker, node) {
|
|
|
161
191
|
if (!type) {
|
|
162
192
|
return true;
|
|
163
193
|
}
|
|
194
|
+
// Check if this is a property access to .children (from PropsWithChildren)
|
|
195
|
+
// This must be checked BEFORE union recursion to avoid false positives
|
|
196
|
+
if (ts.isPropertyAccessExpression(node) && node.name.text === 'children') {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
// Consolidated identifier checks - MUST be before union recursion
|
|
200
|
+
if (ts.isIdentifier(node)) {
|
|
201
|
+
// Destructured or direct `children` parameter (e.g., function Test({ children }: PropsWithChildren))
|
|
202
|
+
if (node.text === 'children') {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
// Check if variable is initialized with JSX (e.g., const element = <div />)
|
|
206
|
+
const symbol = checker.getSymbolAtLocation(node);
|
|
207
|
+
if (symbol) {
|
|
208
|
+
const declarations = symbol.getDeclarations();
|
|
209
|
+
if (declarations) {
|
|
210
|
+
for (const decl of declarations) {
|
|
211
|
+
if (ts.isVariableDeclaration(decl) &&
|
|
212
|
+
decl.initializer &&
|
|
213
|
+
isJsx(ts, decl.initializer)) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
164
220
|
// Any type is never safe
|
|
165
221
|
if (type.flags & ts.TypeFlags.Any) {
|
|
166
222
|
return false;
|
|
167
223
|
}
|
|
224
|
+
// Check type aliases for JSX.Element and Html.Children
|
|
168
225
|
if (type.aliasSymbol) {
|
|
169
|
-
// Allows JSX.Element
|
|
226
|
+
// Allows JSX.Element (when the alias is preserved)
|
|
170
227
|
if (node &&
|
|
171
228
|
type.aliasSymbol.escapedName === 'Element' &&
|
|
172
229
|
// @ts-expect-error - Fast way of checking
|
|
@@ -244,36 +301,42 @@ function proxyObject(obj) {
|
|
|
244
301
|
* Returns more than one node if the node is a binary expression or a conditional
|
|
245
302
|
* expression
|
|
246
303
|
*/
|
|
247
|
-
function getNodeExpressions(node) {
|
|
304
|
+
function getNodeExpressions(ts, node) {
|
|
248
305
|
// Checks operators
|
|
249
|
-
if (
|
|
306
|
+
if (ts.isBinaryExpression(node)) {
|
|
250
307
|
// Ignores operations which results in a boolean
|
|
251
|
-
if (isBooleanBinaryOperatorToken(node.operatorToken)) {
|
|
308
|
+
if (isBooleanBinaryOperatorToken(ts, node.operatorToken)) {
|
|
252
309
|
return [];
|
|
253
310
|
}
|
|
254
|
-
//
|
|
255
|
-
//
|
|
311
|
+
// For && operator, the left side is only rendered when falsy
|
|
312
|
+
// (empty string, null, undefined, 0, false, NaN) - none of which are XSS risks
|
|
313
|
+
// So we only need to diagnose the right side
|
|
314
|
+
if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
|
|
315
|
+
return [node.right];
|
|
316
|
+
}
|
|
317
|
+
// For || and ?? operators, both sides can be rendered with potentially unsafe values
|
|
318
|
+
// So we diagnose both sides
|
|
256
319
|
return [node.left, node.right];
|
|
257
320
|
}
|
|
258
321
|
// Checks the inner expression
|
|
259
|
-
if (
|
|
322
|
+
if (ts.isConditionalExpression(node)) {
|
|
260
323
|
// ignore node.condition because its value will never be rendered
|
|
261
|
-
return [node.
|
|
324
|
+
return [node.whenTrue, node.whenFalse];
|
|
262
325
|
}
|
|
263
326
|
return undefined;
|
|
264
327
|
}
|
|
265
|
-
function isBooleanBinaryOperatorToken(operator) {
|
|
328
|
+
function isBooleanBinaryOperatorToken(ts, operator) {
|
|
266
329
|
switch (operator.kind) {
|
|
267
|
-
case
|
|
268
|
-
case
|
|
269
|
-
case
|
|
270
|
-
case
|
|
271
|
-
case
|
|
272
|
-
case
|
|
273
|
-
case
|
|
274
|
-
case
|
|
275
|
-
case
|
|
276
|
-
case
|
|
330
|
+
case ts.SyntaxKind.EqualsEqualsEqualsToken:
|
|
331
|
+
case ts.SyntaxKind.EqualsEqualsToken:
|
|
332
|
+
case ts.SyntaxKind.ExclamationEqualsEqualsToken:
|
|
333
|
+
case ts.SyntaxKind.ExclamationEqualsToken:
|
|
334
|
+
case ts.SyntaxKind.GreaterThanToken:
|
|
335
|
+
case ts.SyntaxKind.GreaterThanEqualsToken:
|
|
336
|
+
case ts.SyntaxKind.LessThanEqualsToken:
|
|
337
|
+
case ts.SyntaxKind.LessThanToken:
|
|
338
|
+
case ts.SyntaxKind.InstanceOfKeyword:
|
|
339
|
+
case ts.SyntaxKind.InKeyword:
|
|
277
340
|
return true;
|
|
278
341
|
}
|
|
279
342
|
return false;
|
package/dist/util.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeA,MAAY,MAAM,qCAAgB;AAElC,MAAM,SAAS,GAAG,OAAO,CAAA;AACzB,MAAM,iBAAiB,GAAG,qCAAqC,CAAA;AAE/D,+CAA+C;AAC/C,SAAS,KAAK,CACZ,EAAa,EACb,IAAa;IAEb,OAAO,CACL,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,CACpF,CAAA;AACH,CAAC;AAED,sCACE,EAAa,EACb,IAAU,EACV,WAAwB,EACxB,QAAsB;IAEtB,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,SAAS,eAAe,CAAC,IAAI;QACjD,iCAAiC;QACjC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,eAAe,CAAC,CAAA;QAEtC,6BAA6B;QAC7B,IAAI,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;YACpB,oBAAoB;YACpB,kBAAkB,CAAC,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAA;QACrD,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,wBAAwB;IACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,IACE,QAAQ,CAAC,CAAC,CAAE,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAC,KAAK;gBACzC,QAAQ,CAAC,CAAC,CAAE,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAC,MAAM,EAC3C,CAAC;gBACD,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;YACzB,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CACjB,EAAa,EACb,IAAa,EACb,KAA0B,EAC1B,QAA4C;IAE5C,OAAO;QACL,QAAQ,EAAE,EAAE,CAAC,kBAAkB,CAAC,QAAQ,CAAC;QACzC,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO;QAClC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI;QACxB,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE;QAC1B,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE;QACvB,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE;KACvB,CAAA;AACH,CAAC;AAED,4BACE,EAAa,EACb,IAAsD,EACtD,WAAwB,EACxB,WAAyB;IAEzB,2EAA2E;IAC3E,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,gCAAgC;QAChC,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;YACvD,OAAM;QACR,CAAC;QAED,MAAM,aAAa,GAAG,gBAAgB,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;QAE3D,qBAAqB;QACrB,IAAI,aAAa,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnC;YACE,gBAAgB;YAChB,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAC1B,qBAAqB;gBACrB,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC,IAAI,KAAK,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAChF,CAAC;gBACD,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC,CAAA;gBACxE,OAAM;YACR,CAAC;YAED,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAChC;gBACE,8EAA8E;gBAC9E,KAAK,CAAC,EAAE,EAAE,GAAG,CAAC;oBACd,wCAAwC;oBACxC,CAAC,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,EAC/E,CAAC;oBACD,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,aAAa,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC,CAAA;oBACxE,SAAQ;gBACV,CAAC;gBAED,sCAAsC;gBACtC,IACE,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC;oBACvB,uBAAuB;oBACvB,GAAG,CAAC,UAAU,EACd,CAAC;oBACD,mDAAmD;oBACnD,MAAM,WAAW,GAAG,kBAAkB,CAAC,EAAE,EAAE,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;oBAE9E,iFAAiF;oBACjF,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;wBAClD,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,aAAa,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC,CAAA;wBACxE,SAAQ;oBACV,CAAC;oBAED,2BAA2B;oBAC3B,IACE,WAAW,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAC1B,eAAe,CACb,EAAE,EACF,WAAW,CAAC,iBAAiB,CAAC,KAAK,CAAC,EACpC,WAAW,EACX,KAAK,CACN,CACF,EACD,CAAC;wBACD,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC,CAAA;oBAC1E,CAAC;gBACH,CAAC;YACH,CAAC;YAED,OAAM;QACR,CAAC;IACH,CAAC;IAED,8DAA8D;IAC9D,iDAAiD;IACjD,IAAI,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACvD,OAAM;IACR,CAAC;IAED,uBAAuB;IACvB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChC,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7B,SAAQ;QACV,CAAC;QAED,mCAAmC;QACnC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;YACpB,SAAQ;QACV,CAAC;QAED,kBAAkB,CAChB,EAAE,EACF,GAAG,CAAC,UAAU,EACd,WAAW,EACX,WAAW,EACX,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAClF,CAAA;IACH,CAAC;IAED,OAAM;AACR,CAAC;AAED,SAAS,kBAAkB,CACzB,EAAa,EACb,IAAmB,EACnB,WAAwB,EACxB,WAAyB,EACzB,WAAoB;IAEpB,qBAAqB;IACrB,IAAI,EAAE,CAAC,yBAAyB,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,IAAI,GAAG,IAAI,CAAC,UAAU,CAAA;IACxB,CAAC;IAED,2EAA2E;IAC3E,IAAI,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;QACpB,OAAM;IACR,CAAC;IAED,MAAM,WAAW,GAAG,kBAAkB,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;IAEhD,iEAAiE;IACjE,IAAI,WAAW,EAAE,CAAC;QAChB,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;YAChC,kBAAkB,CAAC,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,CAAC,CAAA;QACtE,CAAC;QAED,OAAM;IACR,CAAC;IAED,MAAM,IAAI,GAAG,WAAW,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAA;IAEhD,sBAAsB;IACtB,IAAI,eAAe,CAAC,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC;QACjD,OAAM;IACR,CAAC;IAED,uDAAuD;IACvD,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,IAAI,MAAM,GAAG,KAAK,CAAA;QAElB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACrC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC;gBACpB,SAAQ;YACV,CAAC;YAED,MAAM,GAAG,IAAI,CAAA;YAEb,kBAAkB,CAAC,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,WAAW,CAAC,CAAA;QACvD,CAAC;QAED,uDAAuD;QACvD,oCAAoC;QACpC,IAAI,MAAM,EAAE,CAAC;YACX,OAAM;QACR,CAAC;IACH,CAAC;IAED,kDAAkD;IAClD,IAAI,WAAW,IAAI,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1C,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC,CAAA;IACjE,CAAC;SAAM,CAAC;QACN,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAA;IACxD,CAAC;AACH,CAAC;AAED,yBACE,EAAa,EACb,IAAsB,EACtB,OAAoB,EACpB,IAAa;IAEb,2CAA2C;IAC3C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,IAAI,CAAA;IACb,CAAC;IAED,2EAA2E;IAC3E,uEAAuE;IACvE,IAAI,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QACzE,OAAO,IAAI,CAAA;IACb,CAAC;IAED,kEAAkE;IAClE,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,qGAAqG;QACrG,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAA;QACb,CAAC;QAED,4EAA4E;QAC5E,MAAM,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAA;QAChD,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,YAAY,GAAG,MAAM,CAAC,eAAe,EAAE,CAAA;YAC7C,IAAI,YAAY,EAAE,CAAC;gBACjB,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;oBAChC,IACE,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC;wBAC9B,IAAI,CAAC,WAAW;wBAChB,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,EAC3B,CAAC;wBACD,OAAO,IAAI,CAAA;oBACb,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,yBAAyB;IACzB,IAAI,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;QAClC,OAAO,KAAK,CAAA;IACd,CAAC;IAED,uDAAuD;IACvD,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;QACrB,mDAAmD;QACnD,IACE,IAAI;YACJ,IAAI,CAAC,WAAW,CAAC,WAAW,KAAK,SAAS;YAC1C,0CAA0C;YAC1C,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,KAAK,KAAK;YAC9C,qEAAqE;YACrE,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,EACpD,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC;QAED,uBAAuB;QACvB,IACE,IAAI,CAAC,WAAW,CAAC,WAAW,KAAK,UAAU;YAC3C,+FAA+F;YAC/F,sEAAsE;YACtE,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,KAAK,MAAM;gBAC9C,kFAAkF;gBAClF,sFAAsF;gBACtF,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,qBAAqB,CAAC;gBACpE,mGAAmG;gBACnG,yGAAyG;gBACzG,4HAA4H;gBAC5H,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B,KAAK,MAAM;oBACjD,kFAAkF;oBAClF,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAC3E,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IAED,4CAA4C;IAC5C,IAAI,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC;QACjC,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,eAAe,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAA;IACvF,CAAC;IAED,+DAA+D;IAC/D,IAAI,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,WAAW,KAAK,SAAS,EAAE,CAAC;QACxE,OAAO,eAAe,CAAC,EAAE,EAAG,IAAY,CAAC,qBAAqB,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;IACrF,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;IAE3B,0CAA0C;IAC1C,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAA;IACd,CAAC;IAED,mEAAmE;IACnE,qCAAqC;IACrC;IACE,0CAA0C;IAC1C,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC;QACnC,yCAAyC;QACzC,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,EACnC,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED;IACE,8CAA8C;IAC9C,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;QACvB,mDAAmD;QACnD,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAC7B,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAED,0BAAiC,OAA0B;IACzD,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;QACtD,IAAI,SAAS,CAAC,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC;YACnC,OAAO,SAAS,CAAA;QAClB,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,qBAA8C,GAAM;IAClD,MAAM,KAAK,GAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAEpC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAmB,EAAE,CAAC;QACnD,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAE,CAAA;QACjB,yEAAyE;QACzE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAe,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IACvD,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;GAGG;AACH,SAAS,kBAAkB,CACzB,EAAa,EACb,IAAmB;IAEnB,mBAAmB;IACnB,IAAI,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,gDAAgD;QAChD,IAAI,4BAA4B,CAAC,EAAE,EAAE,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;YACzD,OAAO,EAAE,CAAA;QACX,CAAC;QAED,6DAA6D;QAC7D,+EAA+E;QAC/E,6CAA6C;QAC7C,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,KAAK,EAAE,CAAC,UAAU,CAAC,uBAAuB,EAAE,CAAC;YACtE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACrB,CAAC;QAED,qFAAqF;QACrF,4BAA4B;QAC5B,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;IAChC,CAAC;IAED,8BAA8B;IAC9B,IAAI,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,iEAAiE;QACjE,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;IACxC,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,SAAS,4BAA4B,CAAC,EAAa,EAAE,QAA6B;IAChF,QAAQ,QAAQ,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,EAAE,CAAC,UAAU,CAAC,uBAAuB,CAAC;QAC3C,KAAK,EAAE,CAAC,UAAU,CAAC,iBAAiB,CAAC;QACrC,KAAK,EAAE,CAAC,UAAU,CAAC,4BAA4B,CAAC;QAChD,KAAK,EAAE,CAAC,UAAU,CAAC,sBAAsB,CAAC;QAC1C,KAAK,EAAE,CAAC,UAAU,CAAC,gBAAgB,CAAC;QACpC,KAAK,EAAE,CAAC,UAAU,CAAC,sBAAsB,CAAC;QAC1C,KAAK,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC;QACvC,KAAK,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC;QACjC,KAAK,EAAE,CAAC,UAAU,CAAC,iBAAiB,CAAC;QACrC,KAAK,EAAE,CAAC,UAAU,CAAC,SAAS;YAC1B,OAAO,IAAI,CAAA;IACf,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitajs/ts-html-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-next.1",
|
|
4
4
|
"homepage": "https://github.com/kitajs/html/tree/master/packages/ts-html-plugin#readme",
|
|
5
5
|
"bugs": "https://github.com/kitajs/html/issues",
|
|
6
6
|
"repository": {
|
|
@@ -16,30 +16,38 @@
|
|
|
16
16
|
"main": "dist/index.js",
|
|
17
17
|
"types": "dist/index.d.ts",
|
|
18
18
|
"bin": {
|
|
19
|
-
"ts-html-plugin": "
|
|
20
|
-
"xss-scan": "
|
|
19
|
+
"ts-html-plugin": "bin/index.js",
|
|
20
|
+
"xss-scan": "bin/index.js"
|
|
21
21
|
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"src",
|
|
25
|
+
"!dist/*.tsbuildinfo"
|
|
26
|
+
],
|
|
22
27
|
"dependencies": {
|
|
23
28
|
"chalk": "^5.6.2",
|
|
24
|
-
"tslib": "
|
|
29
|
+
"tslib": "2.8.1",
|
|
25
30
|
"yargs": "^18.0.0"
|
|
26
31
|
},
|
|
27
32
|
"devDependencies": {
|
|
28
|
-
"@
|
|
29
|
-
"@
|
|
30
|
-
"@
|
|
31
|
-
"@
|
|
33
|
+
"@types/node": "24.12.2",
|
|
34
|
+
"@types/yargs": "^17.0.35",
|
|
35
|
+
"@typescript/native-preview": "7.0.0-dev.20260411.1",
|
|
36
|
+
"@vitest/coverage-v8": "4.1.4",
|
|
32
37
|
"fast-defer": "^1.1.9",
|
|
33
|
-
"self": "
|
|
38
|
+
"self-ts-plugin": "link:",
|
|
39
|
+
"typescript": "6.0.2",
|
|
40
|
+
"vitest": "4.1.4",
|
|
41
|
+
"@kitajs/html": "^5.0.0-next.1"
|
|
34
42
|
},
|
|
35
43
|
"peerDependencies": {
|
|
36
|
-
"
|
|
37
|
-
"
|
|
44
|
+
"typescript": "6.0.2",
|
|
45
|
+
"@kitajs/html": "^5.0.0-next.1"
|
|
38
46
|
},
|
|
39
47
|
"scripts": {
|
|
40
|
-
"build": "
|
|
41
|
-
"
|
|
42
|
-
"test": "
|
|
43
|
-
"test-
|
|
48
|
+
"build": "tsgo -p tsconfig.build.json",
|
|
49
|
+
"pretest": "pnpm run build",
|
|
50
|
+
"test": "vitest --coverage --typecheck --run",
|
|
51
|
+
"test-types": "tsgo --noEmit"
|
|
44
52
|
}
|
|
45
53
|
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import ts from 'typescript'
|
|
5
|
+
import yargs from 'yargs'
|
|
6
|
+
import { hideBin } from 'yargs/helpers'
|
|
7
|
+
import { recursiveDiagnoseJsxElements } from './util'
|
|
8
|
+
|
|
9
|
+
const { version } = require('../package.json')
|
|
10
|
+
|
|
11
|
+
const help = `
|
|
12
|
+
|
|
13
|
+
@kitajs/ts-html-plugin v${version} - A CLI tool & TypeScript LSP for finding XSS vulnerabilities in your TypeScript code.
|
|
14
|
+
|
|
15
|
+
Usage: xss-scan [options] <file> <file>...
|
|
16
|
+
ts-html-plugin [options] <file> <file>...
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--cwd <path> The current working directory to use (defaults to process.cwd())
|
|
20
|
+
-p, --project <path> The path to the tsconfig.json file to use (defaults to 'tsconfig.json')
|
|
21
|
+
-s, --simplified Use simplified diagnostics
|
|
22
|
+
-h, --help Show this help message
|
|
23
|
+
--version Show the version number
|
|
24
|
+
<file> <file>... The files to check (defaults to all files in tsconfig.json)
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
$ xss-scan
|
|
28
|
+
$ xss-scan --cwd src
|
|
29
|
+
$ xss-scan --project tsconfig.build.json
|
|
30
|
+
$ xss-scan src/index.tsx src/App.tsx
|
|
31
|
+
|
|
32
|
+
Exit codes:
|
|
33
|
+
0 - No XSS vulnerabilities were found
|
|
34
|
+
1 - XSS vulnerabilities were found
|
|
35
|
+
2 - Only warnings were found
|
|
36
|
+
|
|
37
|
+
`.trim()
|
|
38
|
+
|
|
39
|
+
function readCompilerOptions(tsconfigPath: string) {
|
|
40
|
+
const { config, error } = ts.readConfigFile(tsconfigPath, ts.sys.readFile)
|
|
41
|
+
|
|
42
|
+
if (error) {
|
|
43
|
+
return { errors: [error] }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const { options, errors, fileNames } = ts.parseJsonConfigFileContent(
|
|
47
|
+
config,
|
|
48
|
+
ts.sys,
|
|
49
|
+
path.dirname(tsconfigPath),
|
|
50
|
+
undefined,
|
|
51
|
+
tsconfigPath
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if (errors.length) {
|
|
55
|
+
return { errors }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { options, fileNames, errors: undefined }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function prettyPrintErrorCount(diagnostics: ts.Diagnostic[], root: string) {
|
|
62
|
+
const files = new Map<string, number>()
|
|
63
|
+
|
|
64
|
+
// Counts the amount of errors per file
|
|
65
|
+
for (const diagnostic of diagnostics) {
|
|
66
|
+
if (!diagnostic.file) {
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const file = files.get(diagnostic.file.fileName)
|
|
71
|
+
|
|
72
|
+
if (file !== undefined) {
|
|
73
|
+
files.set(diagnostic.file.fileName, file + 1)
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
files.set(diagnostic.file.fileName, 1)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (files.size > 1) {
|
|
81
|
+
console.error(
|
|
82
|
+
chalk.red(`Found a total of ${diagnostics.length} errors in ${files.size} files\n`)
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const [file, amount] of files.entries()) {
|
|
87
|
+
console.error(
|
|
88
|
+
chalk.red(
|
|
89
|
+
`Found ${amount} error${amount === 1 ? '' : 's'} in ${path.relative(root, file)}`
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function fileExists(p: string) {
|
|
96
|
+
try {
|
|
97
|
+
fs.statSync(p)
|
|
98
|
+
return true
|
|
99
|
+
} catch {
|
|
100
|
+
return false
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function main() {
|
|
105
|
+
const args = await yargs(hideBin(process.argv)).help(false).version(version).argv
|
|
106
|
+
|
|
107
|
+
if (args.help || args.h) {
|
|
108
|
+
console.log(help)
|
|
109
|
+
return process.exit(0)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Detects unknown arguments
|
|
113
|
+
for (const key in args) {
|
|
114
|
+
if (key === '_' || key === '$0') {
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
switch (key) {
|
|
119
|
+
case 'cwd':
|
|
120
|
+
case 'project':
|
|
121
|
+
case 'p':
|
|
122
|
+
case 's':
|
|
123
|
+
case 'simplified':
|
|
124
|
+
continue
|
|
125
|
+
default:
|
|
126
|
+
console.error(`Unknown argument: ${key}. Run --help for more information.`)
|
|
127
|
+
return process.exit(1)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const root = args.cwd ? String(args.cwd) : process.cwd()
|
|
132
|
+
|
|
133
|
+
const tsconfigPath = String(args.project || args.p || 'tsconfig.json')
|
|
134
|
+
|
|
135
|
+
const simplified = !!(args.simplified || args.s)
|
|
136
|
+
|
|
137
|
+
const diagnosticFormatter =
|
|
138
|
+
!process.stdout.isTTY || simplified
|
|
139
|
+
? ts.formatDiagnostics
|
|
140
|
+
: ts.formatDiagnosticsWithColorAndContext
|
|
141
|
+
|
|
142
|
+
if (!fileExists(tsconfigPath)) {
|
|
143
|
+
console.error((!simplified ? chalk.red : String)(`Could not find ${tsconfigPath}`))
|
|
144
|
+
return process.exit(1)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const tsconfig = readCompilerOptions(tsconfigPath)
|
|
148
|
+
|
|
149
|
+
const diagnosticHost: ts.FormatDiagnosticsHost = {
|
|
150
|
+
getCurrentDirectory: ts.sys.getCurrentDirectory,
|
|
151
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
152
|
+
getNewLine: () => ts.sys.newLine
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (tsconfig.errors) {
|
|
156
|
+
console.error(diagnosticFormatter(tsconfig.errors, diagnosticHost))
|
|
157
|
+
return process.exit(1)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let files = tsconfig.fileNames
|
|
161
|
+
|
|
162
|
+
if (args._.length) {
|
|
163
|
+
// Prefer the files passed as arguments, otherwise use the files in tsconfig.json
|
|
164
|
+
files = []
|
|
165
|
+
|
|
166
|
+
for (let i = 0; i < args._.length; i++) {
|
|
167
|
+
const file = String(args._[i])
|
|
168
|
+
|
|
169
|
+
if (!fileExists(file)) {
|
|
170
|
+
console.error(
|
|
171
|
+
(!simplified ? chalk.red : String)(`Could not find provided '${file}' file.`)
|
|
172
|
+
)
|
|
173
|
+
return process.exit(1)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!file.match(/(t|j)sx$/)) {
|
|
177
|
+
console.warn(
|
|
178
|
+
(!simplified ? chalk.yellow : String)(
|
|
179
|
+
`Provided '${file}' file is not a TSX/JSX file.`
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
continue
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
files.push(file)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!files.length) {
|
|
190
|
+
console.error((!simplified ? chalk.red : String)('No files were found to check.'))
|
|
191
|
+
return process.exit(1)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const program = ts.createProgram(files, tsconfig.options)
|
|
195
|
+
const typeChecker = program.getTypeChecker()
|
|
196
|
+
const sources = program.getSourceFiles()
|
|
197
|
+
|
|
198
|
+
const diagnostics: ts.Diagnostic[] = []
|
|
199
|
+
|
|
200
|
+
for (const source of sources) {
|
|
201
|
+
const filename = source.fileName
|
|
202
|
+
|
|
203
|
+
// Not a tsx file, so don't do anything
|
|
204
|
+
if (!filename.match(/(t|j)sx$/)) {
|
|
205
|
+
continue
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
ts.forEachChild(source, function loopSourceNodes(node) {
|
|
209
|
+
recursiveDiagnoseJsxElements(ts, node, typeChecker, diagnostics)
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (diagnostics.length) {
|
|
214
|
+
const hasError = diagnostics.some(
|
|
215
|
+
(diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
console.error(diagnosticFormatter(diagnostics, diagnosticHost))
|
|
219
|
+
|
|
220
|
+
if (!simplified) {
|
|
221
|
+
prettyPrintErrorCount(diagnostics, root)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
process.exit(hasError ? 1 : 2)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
console.log(chalk.green(`No XSS vulnerabilities found in ${files.length} files!`))
|
|
228
|
+
process.exit(0)
|
|
229
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
function createError(code: number, message: string) {
|
|
2
|
+
return {
|
|
3
|
+
code,
|
|
4
|
+
message:
|
|
5
|
+
process.env.INTERNAL_DISABLE_URL_DOCS !== 'true'
|
|
6
|
+
? `${message}\nSee https://html.kitajs.org/TS${code}`
|
|
7
|
+
: message
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// 88600 is the base code for all errors in this plugin.
|
|
12
|
+
// KITA in ASCII
|
|
13
|
+
// 75 * 105 * 116 * 97 = 88609500
|
|
14
|
+
// Simplify to 88600
|
|
15
|
+
|
|
16
|
+
export const Xss = createError(
|
|
17
|
+
88601,
|
|
18
|
+
`Content may introduce an XSS vulnerability and must be marked with the \`safe\` attribute.`
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
export const DoubleEscape = createError(
|
|
22
|
+
88602,
|
|
23
|
+
`The \`safe\` attribute causes this content to be escaped more than once.`
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
export const ComponentXss = createError(
|
|
27
|
+
88603,
|
|
28
|
+
`Content inside a Component must be escaped using escapeHtml().`
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
export const UnusedSafe = createError(
|
|
32
|
+
88604,
|
|
33
|
+
`The \`safe\` attribute is unused in this context.`
|
|
34
|
+
)
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { default as TS, server } from 'typescript/lib/tsserverlibrary'
|
|
2
|
+
import { proxyObject, recursiveDiagnoseJsxElements } from './util'
|
|
3
|
+
|
|
4
|
+
export = function (modules: { typescript: typeof TS }) {
|
|
5
|
+
const ts = modules.typescript
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
create(info: Pick<server.PluginCreateInfo, 'languageService'>) {
|
|
9
|
+
const proxy = proxyObject(info.languageService)
|
|
10
|
+
|
|
11
|
+
proxy.getSemanticDiagnostics = function clonedSemanticDiagnostics(filename) {
|
|
12
|
+
const diagnostics = info.languageService.getSemanticDiagnostics(filename)
|
|
13
|
+
|
|
14
|
+
// Not a tsx file, so don't do anything
|
|
15
|
+
if (!filename.endsWith('.tsx') && !filename.endsWith('.jsx')) {
|
|
16
|
+
return diagnostics
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const program = info.languageService.getProgram()
|
|
20
|
+
|
|
21
|
+
if (!program) {
|
|
22
|
+
return diagnostics
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const source = program.getSourceFile(filename)
|
|
26
|
+
|
|
27
|
+
if (!source) {
|
|
28
|
+
return diagnostics
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const typeChecker = program.getTypeChecker()
|
|
32
|
+
|
|
33
|
+
ts.forEachChild(source, function loopSourceNodes(node) {
|
|
34
|
+
recursiveDiagnoseJsxElements(ts, node, typeChecker, diagnostics)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
return diagnostics
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return proxy
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|