gulpfile.hygiene.js 12.6 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
J
Joao Moreno 已提交
74
	'!**/octicons/**',
M
Miguel Solorio 已提交
75
	'!**/codicon/**',
76 77 78
	'!**/fixtures/**',
	'!**/lib/**',
	'!extensions/**/out/**',
J
Joao Moreno 已提交
79 80
	'!extensions/**/snippets/**',
	'!extensions/**/syntaxes/**',
81
	'!extensions/**/themes/**',
82
	'!extensions/**/colorize-fixtures/**',
83 84 85 86 87

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

J
Joao Moreno 已提交
100
const copyrightFilter = [
J
Joao Moreno 已提交
101
	'**',
102
	'!**/*.desktop',
J
Joao Moreno 已提交
103 104
	'!**/*.json',
	'!**/*.html',
105
	'!**/*.template',
J
Joao Moreno 已提交
106
	'!**/*.md',
107 108
	'!**/*.bat',
	'!**/*.cmd',
109 110
	'!**/*.ico',
	'!**/*.icns',
M
Martin Aeschlimann 已提交
111
	'!**/*.xml',
J
Joao Moreno 已提交
112 113
	'!**/*.sh',
	'!**/*.txt',
D
Daniel Imms 已提交
114
	'!**/*.xpm',
115 116
	'!**/*.opts',
	'!**/*.disabled',
117
	'!**/*.code-workspace',
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',
J
Joao Moreno 已提交
128 129
];

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

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

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

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

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

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

J
Joao Moreno 已提交
185
gulp.task('eslint', () => {
J
Joao Moreno 已提交
186
	return vfs.src(all, { base: '.', follow: true, allowEmpty: true })
J
Joao Moreno 已提交
187 188 189 190 191
		.pipe(filter(eslintFilter))
		.pipe(gulpeslint('src/.eslintrc'))
		.pipe(gulpeslint.formatEach('compact'))
		.pipe(gulpeslint.failAfterError());
});
J
Joao Moreno 已提交
192

J
Joao Moreno 已提交
193
gulp.task('tslint', () => {
194 195 196 197 198 199 200 201 202 203 204 205 206
	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 已提交
207
	]).pipe(es.through());
J
Joao Moreno 已提交
208 209
});

A
Alex Dima 已提交
210 211 212 213 214 215 216 217 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')
		.pipe(es.through(function() {
			checkPackageJSON.call(this, 'remote/package.json');
			checkPackageJSON.call(this, 'remote/web/package.json');
		}));
});
gulp.task(checkPackageJSONTask);


237
function hygiene(some) {
J
Joao Moreno 已提交
238
	let errorCount = 0;
J
Joao Moreno 已提交
239

240 241 242 243 244 245 246 247 248 249 250
	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 已提交
251
	const indentation = es.through(function (file) {
252 253 254 255
		const lines = file.contents.toString('utf8').split(/\r\n|\r|\n/);
		file.__lines = lines;

		lines
J
Joao Moreno 已提交
256
			.forEach((line, i) => {
J
Joao Moreno 已提交
257 258
				if (/^\s*$/.test(line)) {
					// empty or whitespace lines are OK
J
Joao Moreno 已提交
259 260 261 262 263
				} else if (/^[\t]*[^\s]/.test(line)) {
					// good indent
				} else if (/^[\t]* \*/.test(line)) {
					// block comment using an extra space
				} else {
264
					console.error(file.relative + '(' + (i + 1) + ',1): Bad whitespace indentation');
J
Joao Moreno 已提交
265 266 267 268 269 270 271
					errorCount++;
				}
			});

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

J
Joao Moreno 已提交
272
	const copyrights = es.through(function (file) {
273 274 275 276 277 278 279 280
		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 已提交
281
		}
J
Joao Moreno 已提交
282

J
Joao Moreno 已提交
283
		this.emit('data', file);
J
Joao Moreno 已提交
284 285
	});

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

			if (original !== formatted) {
306
				console.error("File not formatted. Run the 'Format Document' command to fix it:", file.relative);
J
Johannes Rieken 已提交
307 308
				errorCount++;
			}
J
Johannes Rieken 已提交
309
			cb(null, file);
J
Johannes Rieken 已提交
310 311

		}, err => {
J
Johannes Rieken 已提交
312
			cb(err);
J
Johannes Rieken 已提交
313 314 315
		});
	});

316 317 318
	const tslintConfiguration = tslint.Configuration.findConfiguration('tslint.json', '.');
	const tslintOptions = { fix: false, formatter: 'json' };
	const tsLinter = new tslint.Linter(tslintOptions);
J
Johannes Rieken 已提交
319

320
	const tsl = es.through(function (file) {
J
Joao Moreno 已提交
321
		const contents = file.contents.toString('utf8');
322
		tsLinter.lint(file.relative, contents, tslintConfiguration.results);
J
Joao Moreno 已提交
323 324
		this.emit('data', file);
	});
325

J
Joao Moreno 已提交
326 327 328 329 330 331 332 333
	let input;

	if (Array.isArray(some) || typeof some === 'string' || !some) {
		input = vfs.src(some || all, { base: '.', follow: true, allowEmpty: true });
	} else {
		input = some;
	}

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

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

346
	let typescript = result
347
		.pipe(filter(tslintHygieneFilter))
348 349 350 351 352
		.pipe(formatting);

	if (!process.argv.some(arg => arg === '--skip-tslint')) {
		typescript = typescript.pipe(tsl);
	}
J
Joao Moreno 已提交
353 354 355 356 357 358 359

	const javascript = result
		.pipe(filter(eslintFilter))
		.pipe(gulpeslint('src/.eslintrc'))
		.pipe(gulpeslint.formatEach('compact'))
		.pipe(gulpeslint.failAfterError());

360
	let count = 0;
J
Joao Moreno 已提交
361
	return es.merge(typescript, javascript)
362 363
		.pipe(es.through(function (data) {
			count++;
J
Joao Moreno 已提交
364
			if (process.env['TRAVIS'] && count % 10 === 0) {
365 366 367 368 369
				process.stdout.write('.');
			}
			this.emit('data', data);
		}, function () {
			process.stdout.write('\n');
370

371 372 373 374 375 376 377 378 379
			const tslintResult = tsLinter.getResult();
			if (tslintResult.failures.length > 0) {
				for (const failure of tslintResult.failures) {
					const name = failure.getFileName();
					const position = failure.getStartPosition();
					const line = position.getLineAndCharacter().line;
					const character = position.getLineAndCharacter().character;

					console.error(`${name}:${line + 1}:${character + 1}:${failure.getFailure()}`);
J
Joao Moreno 已提交
380
				}
381
				errorCount += tslintResult.failures.length;
382 383
			}

J
Joao Moreno 已提交
384
			if (errorCount > 0) {
J
Joao Moreno 已提交
385
				this.emit('error', 'Hygiene failed with ' + errorCount + ' errors. Check \'build/gulpfile.hygiene.js\'.');
J
Joao Moreno 已提交
386 387 388 389
			} else {
				this.emit('end');
			}
		}));
J
Joao Moreno 已提交
390
}
391

J
Joao Moreno 已提交
392 393 394 395 396 397 398 399
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 已提交
400 401 402
			if (err && err.code === 'ENOENT') { // ignore deletions
				return c(null);
			} else if (err) {
J
Joao Moreno 已提交
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
				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 已提交
421 422
	return pall(fns, { concurrency: 4 })
		.then(r => r.filter(p => !!p));
J
Joao Moreno 已提交
423
}
424

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

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

431 432 433 434 435
	process.on('unhandledRejection', (reason, p) => {
		console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
		process.exit(1);
	});

436 437 438 439 440 441 442
	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 已提交
443
		cp.exec('git diff --cached --name-only', { maxBuffer: 2000 * 1024 }, (err, out) => {
J
Joao Moreno 已提交
444 445 446 447
			if (err) {
				console.error();
				console.error(err);
				process.exit(1);
J
Joao Moreno 已提交
448
				return;
J
Joao Moreno 已提交
449
			}
450

J
Joao Moreno 已提交
451
			const some = out
J
Joao Moreno 已提交
452
				.split(/\r?\n/)
J
Joao Moreno 已提交
453
				.filter(l => !!l);
454

J
Joao Moreno 已提交
455
			if (some.length > 0) {
J
Joao Moreno 已提交
456 457 458
				console.log('Reading git index versions...');

				createGitIndexVinyls(some)
459
					.then(vinyls => new Promise((c, e) => hygiene(es.readArray(vinyls))
J
Joao Moreno 已提交
460 461 462 463 464 465 466
						.on('end', () => c())
						.on('error', e)))
					.catch(err => {
						console.error();
						console.error(err);
						process.exit(1);
					});
J
Joao Moreno 已提交
467
			}
468
		});
469
	}
470
}