Skip to content
This repository was archived by the owner on Sep 11, 2023. It is now read-only.

Commit 38e37a1

Browse files
committed
feat: html support
1 parent f9c93e3 commit 38e37a1

14 files changed

+227
-40
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ dist/
55
!test/fixtures/yarn-workspace/node_modules
66
.cache/
77
tmp/
8+
*.vsix

package.json

+6-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Vulnerability Cost",
44
"description": "Display imported vulnerabilities in VS Code",
55
"license": "MIT",
6-
"version": "1.0.0-alpha.3",
6+
"version": "1.0.0-alpha.4",
77
"publisher": "snyk",
88
"scripts": {
99
"DISABLED_postinstall": "node ./node_modules/vscode/bin/install",
@@ -24,7 +24,8 @@
2424
"onLanguage:javascript",
2525
"onLanguage:javascriptreact",
2626
"onLanguage:typescript",
27-
"onLanguage:typescriptreact"
27+
"onLanguage:typescriptreact",
28+
"onLanguage:html"
2829
],
2930
"keywords": [
3031
"import",
@@ -53,17 +54,17 @@
5354
],
5455
"description": "File extensions to be parsed by the JavaScript parser"
5556
},
56-
"vulnCost.javascriptExtensions": {
57+
"vulnCost.htmlExtensions": {
5758
"type": "array",
5859
"default": [
5960
"\\.html?$"
6061
],
6162
"description": "File extensions to be parsed by the HTML parser"
6263
},
63-
"vulnCost.showCalculatingDecoration": {
64+
"vulnCost.showDecoration": {
6465
"type": "boolean",
6566
"default": true,
66-
"description": "Display the 'calculating' decoration when starting to check vulnerability"
67+
"description": "Display the decoration when starting to check vulnerability"
6768
},
6869
"vulnCost.debug": {
6970
"type": "boolean",

src/decorator.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { workspace, window, Range, Position, ThemeColor } from 'vscode';
2-
3-
// import logger from './logger';
2+
import logger from './logger';
43

54
const decorations = {};
65

@@ -10,7 +9,7 @@ export function flushDecorations(fileName, packages) {
109
packages.forEach(packageInfo => {
1110
if (packageInfo.vulns === undefined) {
1211
const configuration = workspace.getConfiguration('vulnCost');
13-
if (configuration.showCalculatingDecoration) {
12+
if (configuration.showDecoration) {
1413
decorate('Scanning for vulns...', packageInfo);
1514
}
1615
} else {
@@ -26,6 +25,11 @@ export function calculated(packageInfo) {
2625
}
2726

2827
function getDecorationMessage(packageInfo) {
28+
logger.log(
29+
`getDecorationMessage - has object? ${!!packageInfo}, has prop? ${!!packageInfo.vulns}, for ${
30+
packageInfo.name
31+
}`
32+
);
2933
if (!packageInfo.vulns || !packageInfo.vulns.count) {
3034
return '';
3135
}

src/extension.js

+28-28
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
getImports,
44
JAVASCRIPT,
55
TYPESCRIPT,
6+
HTML,
67
} from './getImports';
78
import * as vscode from 'vscode';
89
import { calculated, flushDecorations, clearDecorations } from './decorator';
@@ -29,25 +30,17 @@ export function activate(context) {
2930
logger.log('🔓 Using anonymous API');
3031
}
3132

32-
context.subscriptions.push(
33-
vscode.languages.registerCodeActionsProvider(
34-
JAVASCRIPT,
35-
new SnykVulnInfo(),
36-
{
37-
providedCodeActionKinds: SnykVulnInfo.providedCodeActionKinds,
38-
}
39-
)
40-
);
41-
42-
context.subscriptions.push(
43-
vscode.languages.registerCodeActionsProvider(
44-
TYPESCRIPT,
45-
new SnykVulnInfo(),
46-
{
47-
providedCodeActionKinds: SnykVulnInfo.providedCodeActionKinds,
48-
}
49-
)
50-
);
33+
[JAVASCRIPT, TYPESCRIPT, HTML].forEach(language => {
34+
context.subscriptions.push(
35+
vscode.languages.registerCodeActionsProvider(
36+
language,
37+
new SnykVulnInfo(),
38+
{
39+
providedCodeActionKinds: SnykVulnInfo.providedCodeActionKinds,
40+
}
41+
)
42+
);
43+
});
5144

5245
const diagnostics = vscode.languages.createDiagnosticCollection(
5346
'snyk-vulns'
@@ -84,9 +77,7 @@ export function activate(context) {
8477
);
8578

8679
context.subscriptions.push(
87-
commands.registerCommand('vulnCost.showOutput', () => {
88-
logger.show();
89-
})
80+
commands.registerCommand('vulnCost.showOutput', () => logger.show())
9081
);
9182

9283
context.subscriptions.push(
@@ -116,7 +107,7 @@ export function activate(context) {
116107
}
117108
})
118109
.catch(e => {
119-
logger.log(e.message);
110+
logger.log(e.stack);
120111
window.showErrorMessage(e.message);
121112
});
122113
});
@@ -155,15 +146,17 @@ async function processActiveFile(document, diagnostics) {
155146
if (emitters[fileName]) {
156147
emitters[fileName].removeAllListeners();
157148
}
158-
// const { timeout } = workspace.getConfiguration('vulnCost');
149+
159150
emitters[fileName] = getImports(
160151
fileName,
161152
document.getText(),
162153
language(document)
163154
);
164155

165156
emitters[fileName].on('package', createPackageWatcher);
166-
emitters[fileName].on('error', e => logger.log(`vulnCost error: ${e}`));
157+
emitters[fileName].on('error', e =>
158+
logger.log(`vulnCost error: ${e.stack}`)
159+
);
167160
emitters[fileName].on('start', packages => {
168161
flushDecorations(fileName, packages);
169162
});
@@ -184,19 +177,26 @@ function language({ fileName, languageId }) {
184177
const javascriptRegex = new RegExp(
185178
configuration.javascriptExtensions.join('|')
186179
);
180+
const htmlRegex = new RegExp(configuration.htmlExtensions.join('|'));
187181
if (
188182
languageId === 'typescript' ||
189183
languageId === 'typescriptreact' ||
190184
typescriptRegex.test(fileName)
191185
) {
192186
return TYPESCRIPT;
193-
} else if (
187+
}
188+
189+
if (
194190
languageId === 'javascript' ||
195191
languageId === 'javascriptreact' ||
196192
javascriptRegex.test(fileName)
197193
) {
198194
return JAVASCRIPT;
199-
} else {
200-
return undefined;
201195
}
196+
197+
if (languageId === 'html' || htmlRegex.test(fileName)) {
198+
return HTML;
199+
}
200+
201+
return undefined;
202202
}

src/getImports/htmlParser.js

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// import htmlparser2 from 'htmlparser2';
2+
const htmlparser2 = require('htmlparser2');
3+
4+
const JQUERY = 'https://code.jquery.com/';
5+
const MAXCDN = 'https://maxcdn.bootstrapcdn.com/';
6+
const YANDEX = 'https://yastatic.net/';
7+
const pathBased = [MAXCDN, YANDEX];
8+
9+
const JSDELIVR = 'https://cdn.jsdelivr.net/npm/';
10+
const UNPKG = 'https://unpkg.com/';
11+
const atBased = [JSDELIVR, UNPKG];
12+
13+
// packageFromUrl('https://code.jquery.com/jquery-3.0.0-rc1.js') // ?
14+
15+
function packageFromUrl(url) {
16+
let i = url.toLowerCase().indexOf('/ajax/libs/');
17+
url = url.replace(/(.slim)?(\.min)?.js$/, '');
18+
19+
if (i !== -1) {
20+
i += '/ajax/libs/'.length;
21+
let pkg = url.substring(i); // ?
22+
const [name, version = 'latest'] = pkg.split('/'); // ?
23+
return `${name}@${version}`;
24+
}
25+
26+
const isPathBased = pathBased.find(_ => url.toLowerCase().startsWith(_));
27+
28+
if (isPathBased) {
29+
let pkg = url.substring(isPathBased.length); // ?
30+
const [name, version = 'latest'] = pkg.split('/');
31+
return `${name}@${version}`; // ?
32+
}
33+
34+
if (url.toLowerCase().startsWith(JQUERY)) {
35+
let pkg = url.substring(JQUERY.length); // ?
36+
const [name, ...version] = pkg.split('-');
37+
return `${name}@${version.join('-')}`; // ?
38+
}
39+
40+
const isAtBased = atBased.find(_ => url.toLowerCase().startsWith(_));
41+
42+
if (isAtBased) {
43+
let pkg = url
44+
.substring(isAtBased.length)
45+
.split('/')
46+
.shift(); // ?
47+
return pkg;
48+
}
49+
return null;
50+
}
51+
52+
function indexToLineNumber(index, source) {
53+
return source.substring(0, index).split('\n').length;
54+
}
55+
56+
export function getPackages(fileName, html) {
57+
const packages = [];
58+
const parser = new htmlparser2.Parser(
59+
{
60+
onopentag(name, attribs) {
61+
if (
62+
name === 'script' &&
63+
attribs.src &&
64+
(attribs.type || 'javascript/text').toLowerCase() ===
65+
'javascript/text'
66+
) {
67+
const res = packageFromUrl(attribs.src);
68+
69+
if (res) {
70+
const [name, version] = res.split('@');
71+
const line = indexToLineNumber(parser.startIndex, html);
72+
let startCol = html
73+
.substring(parser.startIndex)
74+
.indexOf(attribs.src);
75+
if (startCol === -1) startCol = 0;
76+
packages.push({
77+
loc: {
78+
start: {
79+
line,
80+
column: startCol,
81+
},
82+
end: {
83+
line,
84+
column: startCol + attribs.src.length,
85+
},
86+
},
87+
fileName,
88+
line,
89+
name,
90+
version,
91+
});
92+
}
93+
}
94+
},
95+
},
96+
{ decodeEntities: true }
97+
);
98+
parser.write(html);
99+
parser.end();
100+
return packages;
101+
}

src/getImports/index.js

+8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import {
22
getPackages,
33
TYPESCRIPT as TYPESCRIPT_LANG,
44
JAVASCRIPT as JAVASCRIPT_LANG,
5+
HTML as HTML_LANG,
56
} from './parser';
7+
import nativePackages from './native';
68
import {
79
getPackageInfo,
810
clearPackageCache as _clearPackageCache,
@@ -13,6 +15,7 @@ import validate from 'validate-npm-package-name';
1315

1416
export const TYPESCRIPT = TYPESCRIPT_LANG;
1517
export const JAVASCRIPT = JAVASCRIPT_LANG;
18+
export const HTML = HTML_LANG;
1619
export const clearPackageCache = _clearPackageCache; // this is weird…
1720

1821
export function getImports(fileName, text, language) {
@@ -25,6 +28,10 @@ export function getImports(fileName, text, language) {
2528
}
2629
try {
2730
const imports = getPackages(fileName, text, language).filter(info => {
31+
if (nativePackages.includes(info.name.toLowerCase())) {
32+
return false;
33+
}
34+
2835
if (info.name.startsWith('.')) {
2936
return false;
3037
}
@@ -63,6 +70,7 @@ export function getImports(fileName, text, language) {
6370
const packages = await Promise.all(promises);
6471
emitter.emit('done', packages);
6572
} catch (e) {
73+
console.log(e);
6674
emitter.emit('error', e);
6775
}
6876
}, 0);

src/getImports/native.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
export default [
2+
'assert',
3+
'async_hooks',
4+
'buffer',
5+
'child_process',
6+
'cluster',
7+
'console',
8+
'constants',
9+
'crypto',
10+
'dgram',
11+
'dns',
12+
'domain',
13+
'events',
14+
'fs',
15+
'http',
16+
'http2',
17+
'https',
18+
'inspector',
19+
'internal',
20+
'module',
21+
'net',
22+
'os',
23+
'path',
24+
'perf_hooks',
25+
'process',
26+
'punycode',
27+
'querystring',
28+
'readline',
29+
'repl',
30+
'stream',
31+
'string_decoder',
32+
'sys',
33+
'timers',
34+
'tls',
35+
'trace_events',
36+
'tty',
37+
'url',
38+
'util',
39+
'v8',
40+
'vm',
41+
'wasi',
42+
'worker_threads',
43+
'zlib',
44+
];

0 commit comments

Comments
 (0)