Mercurial > hg > thermostat-ng > web-client
changeset 136:d1612c9b8411
Create equivalent of mvn license:format
Reviwed-by: aazores
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-June/023922.html
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-July/024005.html
line wrap: on
line diff
--- a/integration-test/specs/license-check/license-check.spec.js Wed Jul 19 15:48:35 2017 -0400 +++ b/integration-test/specs/license-check/license-check.spec.js Thu Jul 20 13:49:48 2017 -0400 @@ -119,4 +119,42 @@ let licenseData = licenseCheck.readLicenseFile(CURRENT_LICENSE); licenseData.success.should.be.true(licenseData.error); }); + + describe('argument year processing', () => { + it('should properly get the current year for no arguments', () => { + const CURRENT_YEAR = new Date().getFullYear(); + let currentYear = licenseCheck.getCurrentYearFromArgs([]); + currentYear.should.be.equal(CURRENT_YEAR); + }); + + ['20a5', 'a', ' ', '', '2345 '].forEach(yearStr => { + it('should throw for a non-integer value: "' + yearStr + '"', () => { + licenseCheck.getCurrentYearFromArgs.bind(null, [yearStr]).should.throw(); + }); + }); + + ['1990', '1999', '1000', '1523'].forEach(yearStr => { + it('should throw a < 2000 year value: ' + yearStr, () => { + licenseCheck.getCurrentYearFromArgs.bind(null, [yearStr]).should.throw(); + }); + }); + + ['3000', '3159', '3888', '4813', '9999'].forEach(yearStr => { + it('should throw a >= 3000 year value: ' + yearStr, () => { + licenseCheck.getCurrentYearFromArgs.bind(null, [yearStr]).should.throw(); + }); + }); + + it('should throw if there are multiple arguments', () => { + licenseCheck.getCurrentYearFromArgs.bind(null, ['2014', '2015']).should.throw(); + licenseCheck.getCurrentYearFromArgs.bind(null, ['2014', '2015', '2711']).should.throw(); + }); + + [2014, 2017, 2017, 2050, 2345].forEach(yearInt => { + it('should correctly process valid year ' + yearInt.toString(), () => { + let year = licenseCheck.getCurrentYearFromArgs([yearInt.toString()]); + year.should.be.equal(yearInt); + }); + }); + }); });
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/integration-test/specs/license-check/license-update.spec.js Thu Jul 20 13:49:48 2017 -0400 @@ -0,0 +1,330 @@ +/** + * Copyright 2012-2017 Red Hat, Inc. + * + * Thermostat is distributed under the GNU General Public License, + * version 2 or any later version (with a special exception described + * below, commonly known as the "Classpath Exception"). + * + * A copy of GNU General Public License (GPL) is included in this + * distribution, in the file COPYING. + * + * Linking Thermostat code with other modules is making a combined work + * based on Thermostat. Thus, the terms and conditions of the GPL + * cover the whole combination. + * + * As a special exception, the copyright holders of Thermostat give you + * permission to link this code with independent modules to produce an + * executable, regardless of the license terms of these independent + * modules, and to copy and distribute the resulting executable under + * terms of your choice, provided that you also meet, for each linked + * independent module, the terms and conditions of the license of that + * module. An independent module is a module which is not derived from + * or based on Thermostat code. If you modify Thermostat, you may + * extend this exception to your version of the software, but you are + * not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +describe('License updating', () => { + const licenseCheck = require('../../../license-check/license-check.js'); + + it('should have found the license check module', () => { + (licenseCheck === null).should.be.false(); + }); + + describe('file to array reading', () => { + const SIMPLE_SOURCE_FILE = './integration-test/specs/license-check/resources/source-files/invalid-files/invalid-misc/no-license.js'; + const EMPTY_FILE = './integration-test/specs/license-check/resources/licenses/EMPTY_LICENSE'; + + it('should read a file to lines with no \\r\\n\'s', () => { + let lines = licenseCheck.readFileLinesToArray(SIMPLE_SOURCE_FILE); + const EXPECTED_LINES = [ + 'var x = 5;', + '', + 'function test (val) {', + ' return val;', + '}', + '' + ]; + lines.should.eql(EXPECTED_LINES); + }); + + it('should read an empty file and be an array of one empty string', () => { + let lines = licenseCheck.readFileLinesToArray(EMPTY_FILE); + lines.should.eql(['']); + }); + }); + + describe('license range', () => { + it('should not find anything in an empty list', () => { + let licenseRange = licenseCheck.determineLicenseLineRangesAtTopOfFile(['']); + licenseRange.foundMatch.should.be.false(); + }); + + it('should find it normally at the top', () => { + const LICENSE_LINES = [ + '/**', + ' * Some license', + ' * here', + ' */' + ]; + let licenseRange = licenseCheck.determineLicenseLineRangesAtTopOfFile(LICENSE_LINES); + licenseRange.foundMatch.should.be.true(); + licenseRange.start.should.equal(0); + licenseRange.end.should.equal(3); + }); + + it('should find it normally at the top with source code', () => { + const LICENSE_LINES = [ + '/**', + ' * Some license', + ' * here', + ' */', + '', + 'var sourceCode = "here"', + '' + ]; + let licenseRange = licenseCheck.determineLicenseLineRangesAtTopOfFile(LICENSE_LINES); + licenseRange.foundMatch.should.be.true(); + licenseRange.start.should.equal(0); + licenseRange.end.should.equal(3); + }); + + it('should find it at the middle due to multiple \\n\'s', () => { + const LICENSE_LINES = [ + '', + '', + '', + '/**', + ' * Some license', + ' * here', + ' */', + '', + 'var sourceCode = "here"', + '' + ]; + let licenseRange = licenseCheck.determineLicenseLineRangesAtTopOfFile(LICENSE_LINES); + licenseRange.foundMatch.should.be.true(); + licenseRange.start.should.equal(3); + licenseRange.end.should.equal(6); + }); + + it('should not find it because of source code above it', () => { + const LICENSE_LINES = [ + '', + 'var x = 5;', + '', + '/**', + ' * Some license', + ' * here', + ' */', + '', + 'var sourceCode = "here"', + '' + ]; + let licenseRange = licenseCheck.determineLicenseLineRangesAtTopOfFile(LICENSE_LINES); + licenseRange.foundMatch.should.be.false(); + }); + + it('should find //\'s and at the middle due to multiple \\n\'s', () => { + const LICENSE_LINES = [ + '', + '', + '// Some license', + '// here', + '', + 'var sourceCode = "here"', + '' + ]; + let licenseRange = licenseCheck.determineLicenseLineRangesAtTopOfFile(LICENSE_LINES); + licenseRange.foundMatch.should.be.true(); + licenseRange.start.should.equal(2); + licenseRange.end.should.equal(3); + }); + + it('should report a non-terminated comment document', () => { + const LICENSE_LINES = [ + '/**', + ' * Something 2012-2012', + ' * no terminal star/slash', + 'var sourceCode = "here"', + '' + ]; + let licenseRange = licenseCheck.determineLicenseLineRangesAtTopOfFile(LICENSE_LINES); + licenseRange.foundMatch.should.be.true(); + licenseRange.start.should.equal(0); + licenseRange.end.should.equal(4); + }); + }); + + describe('license range updating', () => { + it('should not find a difference between identical licenses', () => { + const FILE_LINES = [ + '/**', + ' * Some license 2015-2017', + ' */' + ]; + const LICENSE_DATA = { + text: [ + '/**', + ' * Some license 2015-2017', + ' */' + ], + yearLineNumber: 1, + error: null, + success: true + }; + const LICENSE_LINE_RANGE = { start: 0, end: 2, foundMatch: true }; + let updateStatus = licenseCheck.licenseRangeNeedsUpdating(FILE_LINES, LICENSE_LINE_RANGE, LICENSE_DATA); + updateStatus.needsUpdate.should.be.false(); + }); + + it('should find a difference between non-identical licenses (different year)', () => { + const FILE_LINES = [ + '/**', + ' * Some license 2015-2016', + ' */' + ]; + const LICENSE_DATA = { + text: [ + '/**', + ' * Some license 2015-2017', + ' */' + ], + yearLineNumber: 1, + error: null, + success: true + }; + const LICENSE_LINE_RANGE = { start: 0, end: 2, foundMatch: true }; + let updateStatus = licenseCheck.licenseRangeNeedsUpdating(FILE_LINES, LICENSE_DATA, LICENSE_LINE_RANGE); + updateStatus.needsUpdate.should.be.true(); + }); + + it('should find a difference between non-identical licenses (different text)', () => { + const FILE_LINES = [ + '/**', + ' * Some license 2015-2016', + ' *', + ' * This is a simple test', + ' * of mutated text.', + ' */' + ]; + const LICENSE_DATA = { + text: [ + '/**', + ' * Some license 2015-2017', + ' *', + ' * This is a simple test', + ' * of unmutated text.', + ' */' + ], + yearLineNumber: 1, + error: null, + success: true + }; + const LICENSE_LINE_RANGE = { start: 0, end: 5, foundMatch: true }; + let updateStatus = licenseCheck.licenseRangeNeedsUpdating(FILE_LINES, LICENSE_DATA, LICENSE_LINE_RANGE); + updateStatus.needsUpdate.should.be.true(); + }); + + it('should find a difference between non-identical licenses (missing lines)', () => { + const FILE_LINES = [ + '/**', + ' * Some license 2015-2017', + ' *', + ' * This is a simple test', + ' */' + ]; + const LICENSE_DATA = { + text: [ + '/**', + ' * Some license 2015-2017', + ' *', + ' * This is a simple test', + ' * of unmutated text.', + ' */' + ], + yearLineNumber: 1, + error: null, + success: true + }; + const LICENSE_LINE_RANGE = { start: 0, end: 4, foundMatch: true }; + let updateStatus = licenseCheck.licenseRangeNeedsUpdating(FILE_LINES, LICENSE_DATA, LICENSE_LINE_RANGE); + updateStatus.needsUpdate.should.be.true(); + }); + + it('should detect an update is needed for double slash comments', () => { + const FILE_LINES = [ + '// Some license 2015-2016' + ]; + const LICENSE_LINE_RANGE = { start: 0, end: 2, foundMatch: true }; + const LICENSE_DATA = { + text: [ + '/**', + ' * Some license 2015-2017', + ' */' + ], + yearLineNumber: 1, + error: null, + success: true + }; + let updateStatus = licenseCheck.licenseRangeNeedsUpdating(FILE_LINES, LICENSE_DATA, LICENSE_LINE_RANGE); + updateStatus.needsUpdate.should.be.true(); + }); + + it('should detect an update is needed for leaky multiline comments', () => { + const FILE_LINES = [ + '/**', + ' * Some license 2015-2016', + ' */' + ]; + const LICENSE_LINE_RANGE = { start: 0, end: 2, foundMatch: true }; + const LICENSE_DATA = { + text: [ + '/**', + ' * Some license 2015-2017', + ' *' + ], + yearLineNumber: 1, + error: null, + success: true + }; + let updateStatus = licenseCheck.licenseRangeNeedsUpdating(FILE_LINES, LICENSE_DATA, LICENSE_LINE_RANGE); + updateStatus.needsUpdate.should.be.true(); + }); + + it('should not want to update an empty file', () => { + const FILE_LINES = ['']; + const LICENSE_LINE_RANGE = { start: 0, end: 0, foundMatch: true }; + const LICENSE_DATA = { text: [''], yearLineNumber: 0, error: null, success: true }; + let updateStatus = licenseCheck.licenseRangeNeedsUpdating(FILE_LINES, LICENSE_DATA, LICENSE_LINE_RANGE); + updateStatus.needsUpdate.should.be.false(); + }); + }); + + describe('integration test', () => { + const fs = require('fs'); + const glob = require('glob'); + const BROKEN_IDENTIFIER = '-broken.js'; + const EXPECTED_FIXED_IDENTIFIER = '-fixed.js'; + const UPDATABLE_FOLDER = './integration-test/specs/license-check/resources/source-files/updatable-files/'; + const LICENSE_PATH = './integration-test/specs/license-check/resources/licenses/GOOD_LICENSE'; + const LICENSE_DATA = licenseCheck.readLicenseFile(LICENSE_PATH); + + let files = glob.sync(UPDATABLE_FOLDER + '*' + BROKEN_IDENTIFIER); + files.forEach((filePath) => { + it('should properly fix "' + filePath + '"', () => { + let tmpFileLines = licenseCheck.readFileLinesToArray(filePath); + let tmpLicenseLineRange = licenseCheck.determineLicenseLineRangesAtTopOfFile(tmpFileLines); + let updateStatus = licenseCheck.licenseRangeNeedsUpdating(tmpFileLines, tmpLicenseLineRange, LICENSE_DATA); + updateStatus.needsUpdate.should.be.true(); + + let outputText = licenseCheck.generateFileOutputText(tmpFileLines, tmpLicenseLineRange, LICENSE_DATA); + + let fixedPath = filePath.slice(0, filePath.length - BROKEN_IDENTIFIER.length) + EXPECTED_FIXED_IDENTIFIER; + let expectedText = fs.readFileSync(fixedPath, 'utf8'); + outputText.should.be.eql(expectedText); + }); + }); + }); +});
--- a/integration-test/specs/license-check/resources/licenses/GOOD_LICENSE Wed Jul 19 15:48:35 2017 -0400 +++ b/integration-test/specs/license-check/resources/licenses/GOOD_LICENSE Thu Jul 20 13:49:48 2017 -0400 @@ -1,4 +1,4 @@ -Copyright 2015-2016 by some company. +Copyright 2015-2017 by some company. This is a fake license used in unit
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/integration-test/specs/license-check/resources/source-files/updatable-files/corruptDate-broken.js Thu Jul 20 13:49:48 2017 -0400 @@ -0,0 +1,13 @@ +/** + * Copyright 201-2015 by some company. + * + * This is a fake license + * used in unit + * testing. + */ + + + +function f (x) { + return x; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/integration-test/specs/license-check/resources/source-files/updatable-files/corruptDate-fixed.js Thu Jul 20 13:49:48 2017 -0400 @@ -0,0 +1,13 @@ +/** + * Copyright 2015-2017 by some company. + * + * This is a fake license + * used in unit + * testing. + */ + + + +function f (x) { + return x; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/integration-test/specs/license-check/resources/source-files/updatable-files/noEndingJavadoc-broken.js Thu Jul 20 13:49:48 2017 -0400 @@ -0,0 +1,6 @@ +/** + * Copyright 2015-2017 by some company. + * + * This is a fake license + * used in unit + * testing.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/integration-test/specs/license-check/resources/source-files/updatable-files/noEndingJavadoc-fixed.js Thu Jul 20 13:49:48 2017 -0400 @@ -0,0 +1,7 @@ +/** + * Copyright 2015-2017 by some company. + * + * This is a fake license + * used in unit + * testing. + */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/integration-test/specs/license-check/resources/source-files/updatable-files/offsetFromTop-broken.js Thu Jul 20 13:49:48 2017 -0400 @@ -0,0 +1,12 @@ + + + +/** + * Copyright 2015-2017 by some company. + * + * This is a fake license + * used in unit + * testing. + */ + +var x = 5;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/integration-test/specs/license-check/resources/source-files/updatable-files/offsetFromTop-fixed.js Thu Jul 20 13:49:48 2017 -0400 @@ -0,0 +1,9 @@ +/** + * Copyright 2015-2017 by some company. + * + * This is a fake license + * used in unit + * testing. + */ + +var x = 5;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/integration-test/specs/license-check/resources/source-files/updatable-files/outOfDate-broken.js Thu Jul 20 13:49:48 2017 -0400 @@ -0,0 +1,9 @@ +/** + * Copyright 2015-2016 by some company. + * + * This is a fake license + * used in unit + * testing. + */ + +var xyz = 'hi';
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/integration-test/specs/license-check/resources/source-files/updatable-files/outOfDate-fixed.js Thu Jul 20 13:49:48 2017 -0400 @@ -0,0 +1,9 @@ +/** + * Copyright 2015-2017 by some company. + * + * This is a fake license + * used in unit + * testing. + */ + +var xyz = 'hi';
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/integration-test/specs/license-check/resources/source-files/updatable-files/slashComments-broken.js Thu Jul 20 13:49:48 2017 -0400 @@ -0,0 +1,7 @@ +// Copyright 2015-2017 by some company. +// +// This is a fake license +// used in unit +// testing. + +let a = null;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/integration-test/specs/license-check/resources/source-files/updatable-files/slashComments-fixed.js Thu Jul 20 13:49:48 2017 -0400 @@ -0,0 +1,9 @@ +/** + * Copyright 2015-2017 by some company. + * + * This is a fake license + * used in unit + * testing. + */ + +let a = null;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/integration-test/specs/license-check/resources/source-files/updatable-files/slightlyCorrupt-broken.js Thu Jul 20 13:49:48 2017 -0400 @@ -0,0 +1,16 @@ +/** + * Copyright 2015-2017 by some company. + * + * This is a fake license + * used in unit + */ + + + + +var f = () => { }; + + + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/integration-test/specs/license-check/resources/source-files/updatable-files/slightlyCorrupt-fixed.js Thu Jul 20 13:49:48 2017 -0400 @@ -0,0 +1,17 @@ +/** + * Copyright 2015-2017 by some company. + * + * This is a fake license + * used in unit + * testing. + */ + + + + +var f = () => { }; + + + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/integration-test/specs/license-check/resources/source-files/updatable-files/veryCorrupt-broken.js Thu Jul 20 13:49:48 2017 -0400 @@ -0,0 +1,12 @@ +/** + * Copyright 2014-2017 by some company. + * + * a + * 9aid ; + + +var x = 5; +// +h + + / \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/integration-test/specs/license-check/resources/source-files/updatable-files/veryCorrupt-fixed.js Thu Jul 20 13:49:48 2017 -0400 @@ -0,0 +1,7 @@ +/** + * Copyright 2015-2017 by some company. + * + * This is a fake license + * used in unit + * testing. + */
--- a/license-check/license-check.js Wed Jul 19 15:48:35 2017 -0400 +++ b/license-check/license-check.js Thu Jul 20 13:49:48 2017 -0400 @@ -190,7 +190,212 @@ return returnResult; } +/** + * Reads the file at the path provided to an array. If there are any errors + * reading the file, null is returned. Note that the lines do not contain + * any line endings, so [\r]\n's are trimmed away. + * @param {string} filePath The path of the file + * @return An array of lines (with no \n's or \r\n's), or null on error. + */ +function readFileLinesToArray (filePath) { + try { + return fs.readFileSync(filePath, 'utf8').split(/\r?\n/); + } catch (err) { + return null; + } +} + +/** + * A helper function to edit the license range object based on information of + * the javadoc starting at the provided line offset. + * @param {number} lineOffset The offset in the fileLineArray parameter. + * @param {string[]} fileLineArray A list of all the lines + * @return The updated licenseRange for the javadoc comment range. + */ +function findRangeFromJavadocComments (lineOffset, fileLineArray) { + let startLine = lineOffset; + lineOffset++; + + while (lineOffset < fileLineArray.length) { + if (fileLineArray[lineOffset].startsWith(' */')) { + break; + } + lineOffset++; + } + + let licenseRange = { start: startLine, end: lineOffset, foundMatch: true }; + + // In the corner case where it's a malformed header and runs off the end + // due to a lack of terminating */ then we want to correct the line ending + // since it's one ahead due to how it exited the while loop. + if (lineOffset >= fileLineArray.length) { + licenseRange.end--; + } + + return licenseRange; +} + +/** + * A helper function to edit the license range object based on information of + * the double slash comments starting at the provided line offset. + * @param {number} lineOffset The offset in the fileLineArray parameter. + * @param {string[]} fileLineArray A list of all the lines + * @return The updated licenseRange for the double slash comment range. + */ +function findRangeFromDoubleSlashComments (lineOffset, fileLineArray) { + let startLine = lineOffset; + lineOffset++; + + while (lineOffset < fileLineArray.length && fileLineArray[lineOffset].startsWith('//')) { + lineOffset++; + } + + return { start: startLine, end: lineOffset - 1, foundMatch: true }; +} + +/** + * Checks if the license is at the top of the file, if so then the range of the + * license lines are provided in the file in the returned object. If any error + * occurs, the 'error' field is set with a reason (as a string). + * @param {string[]} fileLineArray A list of all the lines, generated by + * the readFileLinesToArray() function. + * @return An object that contains three fields, 'start' and 'end' which are + * indices of the file lines from where the license starts and ends. The 'end' + * field will always be inclusive, so lines[end] will never go out of bounds. + * If there is an issue, 'foundMatch' is set to false (otherwise its true). + */ +function determineLicenseLineRangesAtTopOfFile (fileLineArray) { + let licenseRange = { start: 0, end: 0, foundMatch: false }; + let lineOffset = 0; + + // Skip empty lines at the top if any. + while (lineOffset < fileLineArray.length) { + let currentLine = fileLineArray[lineOffset].replace(/^\s+$/, ''); + if (currentLine !== '') { + break; + } + lineOffset++; + } + + if (lineOffset === fileLineArray.length) { + return licenseRange; + } + + if (fileLineArray[lineOffset].startsWith('/**')) { + return findRangeFromJavadocComments(lineOffset, fileLineArray); + } + + if (fileLineArray[lineOffset].startsWith('//')) { + return findRangeFromDoubleSlashComments(lineOffset, fileLineArray); + } + + return licenseRange; +} + +/** + * Checks if the range provided for the file is in need of fixing. + * @param {string[]} fileLines The lines returned from readFileLinesToArray(). + * @param {Object} licenseLineRange The license file comment range determined + * from determineLicenseLineRangesAtTopOfFile(). This should not have an error + * set or the function will return true early. + * @param {Object} licenseData The license data object for the + * target file from the readLicenseFile() function. + * @return A 'updateStatus' object which has two fields 'needsUpdate' (bool) + * and 'updateReason' (string). If no updates are needed, then updateReason is + * an empty string. + */ +function licenseRangeNeedsUpdating (fileLines, licenseLineRange, licenseData) { + // We do not want to update empty files. + if (fileLines.length === 1 && fileLines[0] === '') { + return { needsUpdate: false, updateReason: '' }; + } + + if (!licenseLineRange.foundMatch || + licenseLineRange.start > 0 || + !fileLines[licenseLineRange.start].startsWith('/**') || + !fileLines[licenseLineRange.end].startsWith(' */')) { + return { needsUpdate: true, updateReason: 'Missing license header' }; + } + + let licenseDataIndex = 0; + for (let index = licenseLineRange.start; index <= licenseLineRange.end; index++) { + if (fileLines[index] !== licenseData.text[licenseDataIndex]) { + return { needsUpdate: true, updateReason: 'Line ' + (index + 1) + ' is different to the license' }; + } + licenseDataIndex++; + } + + return { needsUpdate: false, updateReason: '' }; +} + +/** + * Generates the text output from the provided data that should be written into + * the target file. + * @param {string[]} fileLines The lines of the file at the file path, + * which should be what was retrieved from readFileLinesToArray(filePath). + * @param {Object} licenseLineRange The object with license range + * information from the determineLicenseLineRangesAtTopOfFile() function. + * @param {Object} licenseData The license data object for the + * target file from the readLicenseFile() function. + * @return The text that can be written to a file. + */ +function generateFileOutputText (fileLines, licenseLineRange, licenseData) { + let textToWriteAsLicenseHeader = licenseData.text.join('\n') + '\n'; + let fileBodyText = ''; + + if (licenseLineRange.foundMatch) { + // Since we want to replace it, we will want to remove lines 0 -> end, as any + // extra \n's in there should be removed along with the license. This will + // also handle the case of just replacing the license at the top. + // We made sure that there isn't source code or anything important before the + // starting line, so it is okay to toss that out since it must be empty space + // instead of something important. + fileBodyText = fileLines.slice(licenseLineRange.end + 1).join('\n'); + } else { + // Because we're adding it to the top, we want to put an extra line between + // the new license and the source code, so we add a '\n' at the beginning. + fileBodyText = '\n' + fileLines.join('\n'); + } + + return textToWriteAsLicenseHeader + fileBodyText; +} + +/** + * Gets the current year from the arguments provided. If it is not in the args + * then it will return whatever the current year is. + * @param {string[]} args The runtime arguments (such as: process.argv.slice(2)) + * of which the year will be extracted from. + * @return The year as an integer. + * @throws {string} If the year provided on the command line is not between the + * year 2000 and 3000. The thrown object is a string, which is the reason. + */ +function getCurrentYearFromArgs (args) { + if (args.length === 0) { + return new Date().getFullYear(); + } else if (args.length > 1) { + throw new Error('Currently only support at most one argument, which should be the year'); + } + + let yearArg = args[0]; + if (/^\d{4}$/.test(yearArg)) { + let year = parseInt(yearArg); + if (isNaN(year)) { + throw new Error('Year argument is not a number: ' + yearArg); + } else if (year < 2000 || year >= 3000) { + throw new Error('Year argument is out of range (should be a year in the 2000\'s)'); + } + return year; + } + + throw new Error('Expected argument to be a four digit year'); +} + exports.readLicenseFile = readLicenseFile; exports.checkFileForLicense = checkFileForLicense; exports.walkDirectoryAndCheckLicenses = walkDirectoryAndCheckLicenses; +exports.readFileLinesToArray = readFileLinesToArray; +exports.determineLicenseLineRangesAtTopOfFile = determineLicenseLineRangesAtTopOfFile; +exports.licenseRangeNeedsUpdating = licenseRangeNeedsUpdating; +exports.generateFileOutputText = generateFileOutputText; +exports.getCurrentYearFromArgs = getCurrentYearFromArgs;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/license-check/perform-license-update.js Thu Jul 20 13:49:48 2017 -0400 @@ -0,0 +1,129 @@ +/** + * Copyright 2012-2017 Red Hat, Inc. + * + * Thermostat is distributed under the GNU General Public License, + * version 2 or any later version (with a special exception described + * below, commonly known as the "Classpath Exception"). + * + * A copy of GNU General Public License (GPL) is included in this + * distribution, in the file COPYING. + * + * Linking Thermostat code with other modules is making a combined work + * based on Thermostat. Thus, the terms and conditions of the GPL + * cover the whole combination. + * + * As a special exception, the copyright holders of Thermostat give you + * permission to link this code with independent modules to produce an + * executable, regardless of the license terms of these independent + * modules, and to copy and distribute the resulting executable under + * terms of your choice, provided that you also meet, for each linked + * independent module, the terms and conditions of the license of that + * module. An independent module is a module which is not derived from + * or based on Thermostat code. If you modify Thermostat, you may + * extend this exception to your version of the software, but you are + * not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +const fs = require('fs'); +const glob = require('glob'); +const licenseChecker = require('./license-check.js'); +const SCRIPT_TERMINATION_EXIT_STATUS = 1; +const LICENSE_FILE_PATH = 'LICENSE'; +const CURRENT_YEAR = licenseChecker.getCurrentYearFromArgs(process.argv.slice(2)); + +function exitWithErrorMessage (msg) { + console.error(msg); + process.exit(SCRIPT_TERMINATION_EXIT_STATUS); +} + +function writeTextToFile (filePath, outputText) { + try { + fs.truncateSync(filePath); + fs.writeFileSync(filePath, outputText); + return true; + } catch (ex) { + return false; + } +} + +function updateLicenseYearIfOutOfDate (licenseText, licenseStartYear, licenseEndYear) { + if (CURRENT_YEAR.toString() === licenseEndYear.toString()) { + return; + } + + console.info('Updating license header to year ' + CURRENT_YEAR); + + let licenseYearStr = licenseStartYear + '-' + licenseEndYear; + let newYearStr = licenseStartYear + '-' + CURRENT_YEAR.toString(); + licenseText = licenseText.replace(licenseYearStr, newYearStr); + + try { + writeTextToFile(LICENSE_FILE_PATH, licenseText); + } catch (err) { + exitWithErrorMessage('Unable to update project license file to latest year: ' + err); + } +} + +// Make sure the license is at the current year. +(() => { + let yearRegex = /(2\d{3})-(2\d{3})/; + + // Note that this can throw on linux/mac/windows, but won't on BSD. We let + // the exception be uncaught and have the script invoker read the stack trace + // to figure out what they did wrong (ex: bad file path, bad permissions). + let licenseText = fs.readFileSync(LICENSE_FILE_PATH, 'utf-8'); + if (licenseText === null) { + exitWithErrorMessage('Error trying to read license file'); + } + + let matches = yearRegex.exec(licenseText); + if (matches === null) { + exitWithErrorMessage('Cannot find year line in the license'); + } + let licenseStartYear = matches[1]; + let licenseEndYear = matches[2]; + + updateLicenseYearIfOutOfDate(licenseText, licenseStartYear, licenseEndYear); +})(); + +// Scan and update the files. +(() => { + let licensesInNeedOfFixing = 0; + let licensesFixed = 0; + + let licenseData = licenseChecker.readLicenseFile(LICENSE_FILE_PATH); + if (!licenseData.success) { + exitWithErrorMessage(licenseData.error); + } + + ['*.js', 'src/**/*.js', 'license-check/**/*.js', 'integration-test/specs/license-check/*.js'].forEach((folderPath) => { + glob.sync(folderPath).forEach(filePath => { + let fileLines = licenseChecker.readFileLinesToArray(filePath); + let licenseLineRange = licenseChecker.determineLicenseLineRangesAtTopOfFile(fileLines); + + let updateStatus = licenseChecker.licenseRangeNeedsUpdating(fileLines, licenseLineRange, licenseData); + if (updateStatus.needsUpdate) { + licensesInNeedOfFixing++; + console.info('Fixing license (' + updateStatus.updateReason + ') at: ' + filePath); + + let outputLines = licenseChecker.generateFileOutputText(fileLines, licenseLineRange, licenseData); + let writeResult = writeTextToFile(filePath, outputLines); + if (writeResult) { + licensesFixed++; + } else { + console.error('Unable to write changes to fix license at: ' + filePath); + } + } + }); + }); + + if (licensesInNeedOfFixing === 0) { + console.info('All licenses are up-to-date'); + } else if (licensesInNeedOfFixing === licensesFixed) { + console.info('Fixed ' + licensesFixed + ' license' + (licensesFixed !== 1 ? 's' : '')); + } else { + let licensesNotFixed = licensesInNeedOfFixing - licensesFixed; + exitWithErrorMessage('Unable to fix ' + licensesNotFixed + '/' + licensesInNeedOfFixing + ' licenses'); + } +})();
--- a/package.json Wed Jul 19 15:48:35 2017 -0400 +++ b/package.json Thu Jul 20 13:49:48 2017 -0400 @@ -71,6 +71,7 @@ "scripts": { "clean": "\"$(npm bin)/rimraf\" checkstyle coverage dist test-reports", "license-check": "node license-check/perform-license-check.js", + "license-update": "node license-check/perform-license-update.js", "prelint": "\"$(npm bin)/mkdirp\" checkstyle", "lint-js": "\"$(npm bin)/eslint\" --ext '!.spec.js' -c .eslintrc.yaml src || npm run lint-js-checkstyle", "lint-js-checkstyle": "\"$(npm bin)/eslint\" -c .eslintrc.yaml -f checkstyle -o checkstyle/checkstyle-src.xml src",