gulpfile.hygiene.js 11.7 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');
J
Joao Moreno 已提交
20

J
Joao Moreno 已提交
21 22
/**
 * Hygiene works by creating cascading subsets of all our files and
23 24 25
 * 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 已提交
26 27 28 29
 *
 * all ⊃ eol ⊇ indentation ⊃ copyright ⊃ typescript
 */

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

208
function hygiene(some) {
J
Joao Moreno 已提交
209
	let errorCount = 0;
J
Joao Moreno 已提交
210

211 212 213 214 215 216 217 218 219 220 221
	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 已提交
222
	const indentation = es.through(function (file) {
223 224 225 226
		const lines = file.contents.toString('utf8').split(/\r\n|\r|\n/);
		file.__lines = lines;

		lines
J
Joao Moreno 已提交
227
			.forEach((line, i) => {
J
Joao Moreno 已提交
228 229
				if (/^\s*$/.test(line)) {
					// empty or whitespace lines are OK
J
Joao Moreno 已提交
230 231 232 233 234
				} else if (/^[\t]*[^\s]/.test(line)) {
					// good indent
				} else if (/^[\t]* \*/.test(line)) {
					// block comment using an extra space
				} else {
235
					console.error(file.relative + '(' + (i + 1) + ',1): Bad whitespace indentation');
J
Joao Moreno 已提交
236 237 238 239 240 241 242
					errorCount++;
				}
			});

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

J
Joao Moreno 已提交
243
	const copyrights = es.through(function (file) {
244 245 246 247 248 249 250 251
		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 已提交
252
		}
J
Joao Moreno 已提交
253

J
Joao Moreno 已提交
254
		this.emit('data', file);
J
Joao Moreno 已提交
255 256
	});

J
Johannes Rieken 已提交
257
	const formatting = es.map(function (file, cb) {
J
Johannes Rieken 已提交
258
		tsfmt.processString(file.path, file.contents.toString('utf8'), {
J
Joao Moreno 已提交
259
			verify: false,
J
Johannes Rieken 已提交
260
			tsfmt: true,
E
Erich Gamma 已提交
261
			// verbose: true,
E
Erich Gamma 已提交
262 263 264 265
			// keep checkJS happy
			editorconfig: undefined,
			replace: undefined,
			tsconfig: undefined,
E
Erich Gamma 已提交
266 267 268 269 270 271
			tsconfigFile: undefined,
			tslint: undefined,
			tslintFile: undefined,
			tsfmtFile: undefined,
			vscode: undefined,
			vscodeFile: undefined
J
Johannes Rieken 已提交
272
		}).then(result => {
J
Joao Moreno 已提交
273 274
			let original = result.src.replace(/\r\n/gm, '\n');
			let formatted = result.dest.replace(/\r\n/gm, '\n');
J
Joao Moreno 已提交
275 276

			if (original !== formatted) {
277
				console.error("File not formatted. Run the 'Format Document' command to fix it:", file.relative);
J
Johannes Rieken 已提交
278 279
				errorCount++;
			}
J
Johannes Rieken 已提交
280
			cb(null, file);
J
Johannes Rieken 已提交
281 282

		}, err => {
J
Johannes Rieken 已提交
283
			cb(err);
J
Johannes Rieken 已提交
284 285 286
		});
	});

287 288 289
	const tslintConfiguration = tslint.Configuration.findConfiguration('tslint.json', '.');
	const tslintOptions = { fix: false, formatter: 'json' };
	const tsLinter = new tslint.Linter(tslintOptions);
J
Johannes Rieken 已提交
290

291
	const tsl = es.through(function (file) {
J
Joao Moreno 已提交
292
		const contents = file.contents.toString('utf8');
293
		tsLinter.lint(file.relative, contents, tslintConfiguration.results);
J
Joao Moreno 已提交
294 295
		this.emit('data', file);
	});
296

J
Joao Moreno 已提交
297 298 299 300 301 302 303 304
	let input;

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

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

J
Joao Moreno 已提交
307
	const result = input
J
Joao Moreno 已提交
308
		.pipe(filter(f => !f.stat.isDirectory()))
309
		.pipe(productJsonFilter)
J
Joao Moreno 已提交
310
		.pipe(process.env['BUILD_SOURCEVERSION'] ? es.through() : productJson)
311
		.pipe(productJsonFilter.restore)
J
Joao Moreno 已提交
312 313
		.pipe(filter(indentationFilter))
		.pipe(indentation)
J
Joao Moreno 已提交
314
		.pipe(filter(copyrightFilter))
J
Joao Moreno 已提交
315 316
		.pipe(copyrights);

317
	let typescript = result
318
		.pipe(filter(tslintHygieneFilter))
319 320 321 322 323
		.pipe(formatting);

	if (!process.argv.some(arg => arg === '--skip-tslint')) {
		typescript = typescript.pipe(tsl);
	}
J
Joao Moreno 已提交
324 325 326 327 328 329 330

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

331
	let count = 0;
J
Joao Moreno 已提交
332
	return es.merge(typescript, javascript)
333 334
		.pipe(es.through(function (data) {
			count++;
J
Joao Moreno 已提交
335
			if (process.env['TRAVIS'] && count % 10 === 0) {
336 337 338 339 340
				process.stdout.write('.');
			}
			this.emit('data', data);
		}, function () {
			process.stdout.write('\n');
341

342 343 344 345 346 347 348 349 350
			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 已提交
351
				}
352
				errorCount += tslintResult.failures.length;
353 354
			}

J
Joao Moreno 已提交
355
			if (errorCount > 0) {
J
Joao Moreno 已提交
356
				this.emit('error', 'Hygiene failed with ' + errorCount + ' errors. Check \'build/gulpfile.hygiene.js\'.');
J
Joao Moreno 已提交
357 358 359 360
			} else {
				this.emit('end');
			}
		}));
J
Joao Moreno 已提交
361
}
362

J
Joao Moreno 已提交
363 364 365 366 367 368 369 370
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 已提交
371 372 373
			if (err && err.code === 'ENOENT') { // ignore deletions
				return c(null);
			} else if (err) {
J
Joao Moreno 已提交
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
				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 已提交
392 393
	return pall(fns, { concurrency: 4 })
		.then(r => r.filter(p => !!p));
J
Joao Moreno 已提交
394
}
395

J
Joao Moreno 已提交
396
gulp.task('hygiene', () => hygiene());
397

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

402 403 404 405 406
	process.on('unhandledRejection', (reason, p) => {
		console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
		process.exit(1);
	});

407 408 409 410 411 412 413
	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 已提交
414
		cp.exec('git diff --cached --name-only', { maxBuffer: 2000 * 1024 }, (err, out) => {
J
Joao Moreno 已提交
415 416 417 418
			if (err) {
				console.error();
				console.error(err);
				process.exit(1);
J
Joao Moreno 已提交
419
				return;
J
Joao Moreno 已提交
420
			}
421

J
Joao Moreno 已提交
422
			const some = out
J
Joao Moreno 已提交
423
				.split(/\r?\n/)
J
Joao Moreno 已提交
424
				.filter(l => !!l);
425

J
Joao Moreno 已提交
426
			if (some.length > 0) {
J
Joao Moreno 已提交
427 428 429
				console.log('Reading git index versions...');

				createGitIndexVinyls(some)
430
					.then(vinyls => new Promise((c, e) => hygiene(es.readArray(vinyls))
J
Joao Moreno 已提交
431 432 433 434 435 436 437
						.on('end', () => c())
						.on('error', e)))
					.catch(err => {
						console.error();
						console.error(err);
						process.exit(1);
					});
J
Joao Moreno 已提交
438
			}
439
		});
440
	}
441
}