kibana/packages/kbn-eslint-plugin-eslint/rules/require_license_header.js
Spencer 96206bd092
[eslint] merge custom rules into a single plugin (#33733)
I'd like to add another custom eslint rule, but there isn't a very good place to do that right now. We have the `eslint-plugin-kibana-custom` package, which is super simple but isn't in the `@kbn` namespace and isn't included in the root eslint config, and `@kbn/eslint-plugin-license-header` is too specific, so I've merged those two packages into `@kbn/eslint-plugin-eslint`, which is a little redundant but allows is to refer to the rules within it as `@kbn/eslint/{rule}`, which feels nice.

Thoughts?

_**NOTE:**_ merging the eslint rules from the two packages means enabling prettier for the code from `@kbn/eslint-plugin-license-header`, all those changes are made in 42c7da6fe2. [View the changes without the prettier updates](b647f2b...74e07a0)
2019-03-22 17:12:14 -07:00

120 lines
3.7 KiB
JavaScript

/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
const babelEslint = require('babel-eslint');
const { assert, normalizeWhitespace, init } = require('../lib');
function isHashbang(text) {
return text.trim().startsWith('#!') && !text.trim().includes('\n');
}
module.exports = {
meta: {
fixable: 'code',
schema: [
{
type: 'object',
properties: {
license: {
type: 'string',
},
},
additionalProperties: false,
},
],
},
create: context => {
return {
Program(program) {
const license = init(context, program, function() {
const options = context.options[0] || {};
const license = options.license;
assert(!!license, '"license" option is required');
const parsed = babelEslint.parse(license);
assert(!parsed.body.length, '"license" option must only include a single comment');
assert(
parsed.comments.length === 1,
'"license" option must only include a single comment'
);
return {
source: license,
nodeValue: normalizeWhitespace(parsed.comments[0].value),
};
});
if (!license) {
return;
}
const sourceCode = context.getSourceCode();
const comment = sourceCode
.getAllComments()
.find(node => normalizeWhitespace(node.value) === license.nodeValue);
// no licence comment
if (!comment) {
context.report({
message: 'File must start with a license header',
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: sourceCode.lines[0].length - 1 },
},
fix(fixer) {
if (isHashbang(sourceCode.lines[0])) {
return undefined;
}
return fixer.replaceTextRange([0, 0], license.source + '\n\n');
},
});
return;
}
// ensure there is nothing before the comment
const sourceBeforeNode = sourceCode
.getText()
.slice(0, sourceCode.getIndexFromLoc(comment.loc.start));
if (sourceBeforeNode.length && !isHashbang(sourceBeforeNode)) {
context.report({
node: comment,
message: 'License header must be at the very beginning of the file',
fix(fixer) {
// replace leading whitespace if possible
if (sourceBeforeNode.trim() === '') {
return fixer.replaceTextRange([0, sourceBeforeNode.length], '');
}
// inject content at top and remove node from current location
// if removing whitespace is not possible
return [
fixer.remove(comment),
fixer.replaceTextRange([0, 0], license.source + '\n\n'),
];
},
});
}
},
};
},
};