gulpfile.hygiene.js 12.2 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

J
Joao Moreno 已提交
6
'use strict';
J
Joao Moreno 已提交
7

J
Joao Moreno 已提交
8 9 10 11
const gulp = require('gulp');
const filter = require('gulp-filter');
const es = require('event-stream');
const gulptslint = require('gulp-tslint');
J
Joao Moreno 已提交
12
const gulpeslint = require('gulp-eslint');
J
Johannes Rieken 已提交
13
const tsfmt = require('typescript-formatter');
J
Joao Moreno 已提交
14
const tslint = require('tslint');
J
Joao Moreno 已提交
15
const VinylFile = require('vinyl');
J
Joao Moreno 已提交
16
const vfs = require('vinyl-fs');
17 18
const path = require('path');
const fs = require('fs');
J
Joao Moreno 已提交
19
const pall = require('p-all');
A
Alex Dima 已提交
20
const task = require('./lib/task');
J
Joao Moreno 已提交
21

J
Joao Moreno 已提交
22 23
/**
 * Hygiene works by creating cascading subsets of all our files and
24 25 26
 * passing them through a sequence of checks. Here are the current subsets,
 * named according to the checks performed on them. Each subset contains
 * the following one, as described in mathematical notation:
J
Joao Moreno 已提交
27 28 29 30
 *
 * all ⊃ eol ⊇ indentation ⊃ copyright ⊃ typescript
 */

J
Joao Moreno 已提交
31
const all = [
32
	'*',
J
Joao Moreno 已提交
33 34 35 36
	'build/**/*',
	'extensions/**/*',
	'scripts/**/*',
	'src/**/*',
37 38
	'test/**/*',
	'!**/node_modules/**'
39 40
];

41
const indentationFilter = [
42
	'**',
43 44

	// except specific files
45
	'!ThirdPartyNotices.txt',
J
Joao Moreno 已提交
46 47
	'!LICENSE.{txt,rtf}',
	'!LICENSES.chromium.html',
A
Alex Dima 已提交
48
	'!**/LICENSE',
49
	'!src/vs/nls.js',
A
Alex Dima 已提交
50
	'!src/vs/nls.build.js',
51
	'!src/vs/css.js',
A
Alex Dima 已提交
52
	'!src/vs/css.build.js',
53
	'!src/vs/loader.js',
54
	'!src/vs/base/common/insane/insane.js',
A
Alex Dima 已提交
55
	'!src/vs/base/common/marked/marked.js',
56
	'!src/vs/base/node/terminateProcess.sh',
57
	'!src/vs/base/node/cpuUsage.sh',
58 59 60
	'!test/assert.js',

	// except specific folders
J
Jason Ginchereau 已提交
61
	'!test/automation/out/**',
B
Benjamin Pasero 已提交
62
	'!test/smoke/out/**',
63 64
	'!extensions/vscode-api-tests/testWorkspace/**',
	'!extensions/vscode-api-tests/testWorkspace2/**',
65
	'!build/monaco/**',
J
Joao Moreno 已提交
66
	'!build/win32/**',
J
Joao Moreno 已提交
67

68
	// except multiple specific files
J
Joao Moreno 已提交
69
	'!**/package.json',
70
	'!**/yarn.lock',
P
Pine Wu 已提交
71
	'!**/yarn-error.log',
72 73

	// except multiple specific folders
M
Miguel Solorio 已提交
74
	'!**/codicon/**',
75 76 77
	'!**/fixtures/**',
	'!**/lib/**',
	'!extensions/**/out/**',
J
Joao Moreno 已提交
78 79
	'!extensions/**/snippets/**',
	'!extensions/**/syntaxes/**',
80
	'!extensions/**/themes/**',
81
	'!extensions/**/colorize-fixtures/**',
82 83 84 85 86

	// except specific file types
	'!src/vs/*/**/*.d.ts',
	'!src/typings/**/*.d.ts',
	'!extensions/**/*.d.ts',
87
	'!**/*.{svg,exe,png,bmp,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe,ico,icns}',
88
	'!build/{lib,tslintRules,download}/**/*.js',
89
	'!build/**/*.sh',
J
Joao Moreno 已提交
90 91
	'!build/azure-pipelines/**/*.js',
	'!build/azure-pipelines/**/*.config',
92
	'!**/Dockerfile',
A
Alex Dima 已提交
93
	'!**/Dockerfile.*',
A
Alex Dima 已提交
94 95
	'!**/*.Dockerfile',
	'!**/*.dockerfile',
96
	'!extensions/markdown-language-features/media/*.js'
J
Joao Moreno 已提交
97 98
];

J
Joao Moreno 已提交
99
const copyrightFilter = [
J
Joao Moreno 已提交
100
	'**',
101
	'!**/*.desktop',
J
Joao Moreno 已提交
102 103
	'!**/*.json',
	'!**/*.html',
104
	'!**/*.template',
J
Joao Moreno 已提交
105
	'!**/*.md',
106 107
	'!**/*.bat',
	'!**/*.cmd',
108 109
	'!**/*.ico',
	'!**/*.icns',
M
Martin Aeschlimann 已提交
110
	'!**/*.xml',
J
Joao Moreno 已提交
111 112
	'!**/*.sh',
	'!**/*.txt',
D
Daniel Imms 已提交
113
	'!**/*.xpm',
114 115
	'!**/*.opts',
	'!**/*.disabled',
116
	'!**/*.code-workspace',
A
Alex Dima 已提交
117
	'!**/*.js.map',
118
	'!**/promise-polyfill/polyfill.js',
J
Joao Moreno 已提交
119
	'!build/**/*.init',
120
	'!resources/linux/snap/snapcraft.yaml',
J
Joao Moreno 已提交
121
	'!resources/linux/snap/electron-launch',
122
	'!resources/win32/bin/code.js',
123
	'!resources/completions/**',
124
	'!extensions/markdown-language-features/media/highlight.css',
125
	'!extensions/html-language-features/server/src/modes/typescript/*',
A
Alex Dima 已提交
126 127
	'!extensions/*/server/bin/*',
	'!src/vs/editor/test/node/classification/typescript-test.ts',
B
Benjamin Pasero 已提交
128
	'!scripts/code-web.js'
J
Joao Moreno 已提交
129 130
];

J
Joao Moreno 已提交
131 132
const eslintFilter = [
	'src/**/*.js',
133
	'build/gulpfile.*.js',
J
Joao Moreno 已提交
134
	'!src/vs/loader.js',
135
	'!src/vs/css.js',
J
Joao Moreno 已提交
136
	'!src/vs/nls.js',
137 138
	'!src/vs/css.build.js',
	'!src/vs/nls.build.js',
139
	'!src/**/insane.js',
A
Alex Dima 已提交
140
	'!src/**/marked.js',
J
Joao Moreno 已提交
141 142 143
	'!**/test/**'
];

144
const tslintBaseFilter = [
145
	'!**/fixtures/**',
J
Joao Moreno 已提交
146
	'!**/typings/**',
B
Benjamin Pasero 已提交
147
	'!**/node_modules/**',
148
	'!extensions/typescript-basics/test/colorize-fixtures/**',
E
Erich Gamma 已提交
149
	'!extensions/vscode-api-tests/testWorkspace/**',
150
	'!extensions/vscode-api-tests/testWorkspace2/**',
E
Erich Gamma 已提交
151
	'!extensions/**/*.test.ts',
152
	'!extensions/html-language-features/server/lib/jquery.d.ts'
J
Joao Moreno 已提交
153 154
];

155 156 157 158
const tslintCoreFilter = [
	'src/**/*.ts',
	'test/**/*.ts',
	'!extensions/**/*.ts',
J
Jason Ginchereau 已提交
159
	'!test/automation/**',
160 161 162 163 164 165 166 167
	'!test/smoke/**',
	...tslintBaseFilter
];

const tslintExtensionsFilter = [
	'extensions/**/*.ts',
	'!src/**/*.ts',
	'!test/**/*.ts',
J
Jason Ginchereau 已提交
168
	'test/automation/**/*.ts',
169 170 171 172 173 174 175 176 177 178
	...tslintBaseFilter
];

const tslintHygieneFilter = [
	'src/**/*.ts',
	'test/**/*.ts',
	'extensions/**/*.ts',
	...tslintBaseFilter
];

179
const copyrightHeaderLines = [
J
Joao Moreno 已提交
180 181 182 183
	'/*---------------------------------------------------------------------------------------------',
	' *  Copyright (c) Microsoft Corporation. All rights reserved.',
	' *  Licensed under the MIT License. See License.txt in the project root for license information.',
	' *--------------------------------------------------------------------------------------------*/'
184
];
J
Joao Moreno 已提交
185

J
Joao Moreno 已提交
186
gulp.task('eslint', () => {
J
Joao Moreno 已提交
187
	return vfs.src(all, { base: '.', follow: true, allowEmpty: true })
188
		.pipe(filter(eslintFilter.concat(tslintHygieneFilter)))
189 190 191 192
		.pipe(gulpeslint({
			configFile: '.eslintrc.json',
			rulePaths: ['./build/lib/eslint']
		}))
J
Joao Moreno 已提交
193
		.pipe(gulpeslint.formatEach('compact'))
194 195 196 197 198
		.pipe(gulpeslint.results(results => {
			if (results.warningCount > 0 || results.errorCount > 0) {
				throw new Error('eslint failed with warnings and/or errors');
			}
		}));
J
Joao Moreno 已提交
199
});
J
Joao Moreno 已提交
200

J
Joao Moreno 已提交
201
gulp.task('tslint', () => {
202 203 204 205 206 207 208 209 210 211 212 213 214
	return es.merge([

		// Core: include type information (required by certain rules like no-nodejs-globals)
		vfs.src(all, { base: '.', follow: true, allowEmpty: true })
			.pipe(filter(tslintCoreFilter))
			.pipe(gulptslint.default({ rulesDirectory: 'build/lib/tslint', program: tslint.Linter.createProgram('src/tsconfig.json') }))
			.pipe(gulptslint.default.report({ emitError: true })),

		// Exenstions: do not include type information
		vfs.src(all, { base: '.', follow: true, allowEmpty: true })
			.pipe(filter(tslintExtensionsFilter))
			.pipe(gulptslint.default({ rulesDirectory: 'build/lib/tslint' }))
			.pipe(gulptslint.default.report({ emitError: true }))
B
Benjamin Pasero 已提交
215
	]).pipe(es.through());
J
Joao Moreno 已提交
216 217
});

A
Alex Dima 已提交
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
function checkPackageJSON(actualPath) {
	const actual = require(path.join(__dirname, '..', actualPath));
	const rootPackageJSON = require('../package.json');

	for (let depName in actual.dependencies) {
		const depVersion = actual.dependencies[depName];
		const rootDepVersion = rootPackageJSON.dependencies[depName];
		if (!rootDepVersion) {
			// missing in root is allowed
			continue;
		}
		if (depVersion !== rootDepVersion) {
			this.emit('error', `The dependency ${depName} in '${actualPath}' (${depVersion}) is different than in the root package.json (${rootDepVersion})`);
		}
	}
}

const checkPackageJSONTask = task.define('check-package-json', () => {
	return gulp.src('package.json')
237
		.pipe(es.through(function () {
A
Alex Dima 已提交
238 239 240 241 242 243 244
			checkPackageJSON.call(this, 'remote/package.json');
			checkPackageJSON.call(this, 'remote/web/package.json');
		}));
});
gulp.task(checkPackageJSONTask);


245
function hygiene(some) {
J
Joao Moreno 已提交
246
	let errorCount = 0;
J
Joao Moreno 已提交
247

248 249 250 251 252 253 254 255 256 257 258
	const productJson = es.through(function (file) {
		const product = JSON.parse(file.contents.toString('utf8'));

		if (product.extensionsGallery) {
			console.error('product.json: Contains "extensionsGallery"');
			errorCount++;
		}

		this.emit('data', file);
	});

J
Joao Moreno 已提交
259
	const indentation = es.through(function (file) {
260 261 262 263
		const lines = file.contents.toString('utf8').split(/\r\n|\r|\n/);
		file.__lines = lines;

		lines
J
Joao Moreno 已提交
264
			.forEach((line, i) => {
J
Joao Moreno 已提交
265 266
				if (/^\s*$/.test(line)) {
					// empty or whitespace lines are OK
J
Joao Moreno 已提交
267 268 269 270 271
				} else if (/^[\t]*[^\s]/.test(line)) {
					// good indent
				} else if (/^[\t]* \*/.test(line)) {
					// block comment using an extra space
				} else {
272
					console.error(file.relative + '(' + (i + 1) + ',1): Bad whitespace indentation');
J
Joao Moreno 已提交
273 274 275 276 277 278 279
					errorCount++;
				}
			});

		this.emit('data', file);
	});

J
Joao Moreno 已提交
280
	const copyrights = es.through(function (file) {
281 282 283 284 285 286 287 288
		const lines = file.__lines;

		for (let i = 0; i < copyrightHeaderLines.length; i++) {
			if (lines[i] !== copyrightHeaderLines[i]) {
				console.error(file.relative + ': Missing or bad copyright statement');
				errorCount++;
				break;
			}
J
Joao Moreno 已提交
289
		}
J
Joao Moreno 已提交
290

J
Joao Moreno 已提交
291
		this.emit('data', file);
J
Joao Moreno 已提交
292 293
	});

J
Johannes Rieken 已提交
294
	const formatting = es.map(function (file, cb) {
J
Johannes Rieken 已提交
295
		tsfmt.processString(file.path, file.contents.toString('utf8'), {
J
Joao Moreno 已提交
296
			verify: false,
J
Johannes Rieken 已提交
297
			tsfmt: true,
E
Erich Gamma 已提交
298
			// verbose: true,
E
Erich Gamma 已提交
299 300 301 302
			// keep checkJS happy
			editorconfig: undefined,
			replace: undefined,
			tsconfig: undefined,
E
Erich Gamma 已提交
303 304 305 306 307 308
			tsconfigFile: undefined,
			tslint: undefined,
			tslintFile: undefined,
			tsfmtFile: undefined,
			vscode: undefined,
			vscodeFile: undefined
J
Johannes Rieken 已提交
309
		}).then(result => {
J
Joao Moreno 已提交
310 311
			let original = result.src.replace(/\r\n/gm, '\n');
			let formatted = result.dest.replace(/\r\n/gm, '\n');
J
Joao Moreno 已提交
312 313

			if (original !== formatted) {
314
				console.error('File not formatted. Run the \'Format Document\' command to fix it:', file.relative);
J
Johannes Rieken 已提交
315 316
				errorCount++;
			}
J
Johannes Rieken 已提交
317
			cb(null, file);
J
Johannes Rieken 已提交
318 319

		}, err => {
J
Johannes Rieken 已提交
320
			cb(err);
J
Johannes Rieken 已提交
321 322 323
		});
	});

J
Joao Moreno 已提交
324 325 326
	let input;

	if (Array.isArray(some) || typeof some === 'string' || !some) {
327 328
		const options = { base: '.', follow: true, allowEmpty: true };
		if (some) {
A
Anthony Dresser 已提交
329
			input = vfs.src(some, options).pipe(filter(all)); // split this up to not unnecessarily filter all a second time
330 331 332
		} else {
			input = vfs.src(all, options);
		}
J
Joao Moreno 已提交
333 334 335 336
	} else {
		input = some;
	}

337 338
	const productJsonFilter = filter('product.json', { restore: true });

J
Joao Moreno 已提交
339
	const result = input
J
Joao Moreno 已提交
340
		.pipe(filter(f => !f.stat.isDirectory()))
341
		.pipe(productJsonFilter)
J
Joao Moreno 已提交
342
		.pipe(process.env['BUILD_SOURCEVERSION'] ? es.through() : productJson)
343
		.pipe(productJsonFilter.restore)
J
Joao Moreno 已提交
344 345
		.pipe(filter(indentationFilter))
		.pipe(indentation)
J
Joao Moreno 已提交
346
		.pipe(filter(copyrightFilter))
J
Joao Moreno 已提交
347 348
		.pipe(copyrights);

349
	const typescript = result
350
		.pipe(filter(tslintHygieneFilter))
351 352
		.pipe(formatting);

J
Joao Moreno 已提交
353
	const javascript = result
354
		.pipe(filter(eslintFilter.concat(tslintHygieneFilter)))
355 356 357 358
		.pipe(gulpeslint({
			configFile: '.eslintrc.json',
			rulePaths: ['./build/lib/eslint']
		}))
J
Joao Moreno 已提交
359
		.pipe(gulpeslint.formatEach('compact'))
360 361 362 363
		.pipe(gulpeslint.results(results => {
			errorCount += results.warningCount;
			errorCount += results.errorCount;
		}));
J
Joao Moreno 已提交
364

365
	let count = 0;
J
Joao Moreno 已提交
366
	return es.merge(typescript, javascript)
367 368
		.pipe(es.through(function (data) {
			count++;
J
Joao Moreno 已提交
369
			if (process.env['TRAVIS'] && count % 10 === 0) {
370 371 372 373 374
				process.stdout.write('.');
			}
			this.emit('data', data);
		}, function () {
			process.stdout.write('\n');
J
Joao Moreno 已提交
375
			if (errorCount > 0) {
J
Joao Moreno 已提交
376
				this.emit('error', 'Hygiene failed with ' + errorCount + ' errors. Check \'build/gulpfile.hygiene.js\'.');
J
Joao Moreno 已提交
377 378 379 380
			} else {
				this.emit('end');
			}
		}));
J
Joao Moreno 已提交
381
}
382

J
Joao Moreno 已提交
383 384 385 386 387 388 389 390
function createGitIndexVinyls(paths) {
	const cp = require('child_process');
	const repositoryPath = process.cwd();

	const fns = paths.map(relativePath => () => new Promise((c, e) => {
		const fullPath = path.join(repositoryPath, relativePath);

		fs.stat(fullPath, (err, stat) => {
J
Joao Moreno 已提交
391 392 393
			if (err && err.code === 'ENOENT') { // ignore deletions
				return c(null);
			} else if (err) {
J
Joao Moreno 已提交
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
				return e(err);
			}

			cp.exec(`git show :${relativePath}`, { maxBuffer: 2000 * 1024, encoding: 'buffer' }, (err, out) => {
				if (err) {
					return e(err);
				}

				c(new VinylFile({
					path: fullPath,
					base: repositoryPath,
					contents: out,
					stat
				}));
			});
		});
	}));

J
Joao Moreno 已提交
412 413
	return pall(fns, { concurrency: 4 })
		.then(r => r.filter(p => !!p));
J
Joao Moreno 已提交
414
}
415

A
Alex Dima 已提交
416
gulp.task('hygiene', task.series(checkPackageJSONTask, () => hygiene()));
417

J
Joao Moreno 已提交
418
// this allows us to run hygiene as a git pre-commit hook
419
if (require.main === module) {
J
Joao Moreno 已提交
420 421
	const cp = require('child_process');

422 423 424 425 426
	process.on('unhandledRejection', (reason, p) => {
		console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
		process.exit(1);
	});

427 428 429 430 431 432 433
	if (process.argv.length > 2) {
		hygiene(process.argv.slice(2)).on('error', err => {
			console.error();
			console.error(err);
			process.exit(1);
		});
	} else {
J
Joao Moreno 已提交
434
		cp.exec('git diff --cached --name-only', { maxBuffer: 2000 * 1024 }, (err, out) => {
J
Joao Moreno 已提交
435 436 437 438
			if (err) {
				console.error();
				console.error(err);
				process.exit(1);
J
Joao Moreno 已提交
439
				return;
J
Joao Moreno 已提交
440
			}
441

J
Joao Moreno 已提交
442
			const some = out
J
Joao Moreno 已提交
443
				.split(/\r?\n/)
J
Joao Moreno 已提交
444
				.filter(l => !!l);
445

J
Joao Moreno 已提交
446
			if (some.length > 0) {
J
Joao Moreno 已提交
447 448 449
				console.log('Reading git index versions...');

				createGitIndexVinyls(some)
450
					.then(vinyls => new Promise((c, e) => hygiene(es.readArray(vinyls))
J
Joao Moreno 已提交
451 452 453 454 455 456 457
						.on('end', () => c())
						.on('error', e)))
					.catch(err => {
						console.error();
						console.error(err);
						process.exit(1);
					});
J
Joao Moreno 已提交
458
			}
459
		});
460
	}
461
}