ohctechv3/.svn/pristine/12/12e91da5693e49c98923d371989e916c255a4ae7.svn-base
2024-10-28 15:03:36 +05:30

324 lines
10 KiB
Plaintext

/**
* @fileoverview Validates whitespace in and around the JSX opening and closing brackets
* @author Diogo Franco (Kovensky)
*/
'use strict';
const getTokenBeforeClosingBracket = require('../util/getTokenBeforeClosingBracket');
const docsUrl = require('../util/docsUrl');
const report = require('../util/report');
const messages = {
selfCloseSlashNoSpace: 'Whitespace is forbidden between `/` and `>`; write `/>`',
selfCloseSlashNeedSpace: 'Whitespace is required between `/` and `>`; write `/ >`',
closeSlashNoSpace: 'Whitespace is forbidden between `<` and `/`; write `</`',
closeSlashNeedSpace: 'Whitespace is required between `<` and `/`; write `< /`',
beforeSelfCloseNoSpace: 'A space is forbidden before closing bracket',
beforeSelfCloseNeedSpace: 'A space is required before closing bracket',
beforeSelfCloseNeedNewline: 'A newline is required before closing bracket',
afterOpenNoSpace: 'A space is forbidden after opening bracket',
afterOpenNeedSpace: 'A space is required after opening bracket',
beforeCloseNoSpace: 'A space is forbidden before closing bracket',
beforeCloseNeedSpace: 'Whitespace is required before closing bracket',
beforeCloseNeedNewline: 'A newline is required before closing bracket',
};
// ------------------------------------------------------------------------------
// Validators
// ------------------------------------------------------------------------------
function validateClosingSlash(context, node, option) {
const sourceCode = context.getSourceCode();
let adjacent;
if (node.selfClosing) {
const lastTokens = sourceCode.getLastTokens(node, 2);
adjacent = !sourceCode.isSpaceBetweenTokens(lastTokens[0], lastTokens[1]);
if (option === 'never') {
if (!adjacent) {
report(context, messages.selfCloseSlashNoSpace, 'selfCloseSlashNoSpace', {
node,
loc: {
start: lastTokens[0].loc.start,
end: lastTokens[1].loc.end,
},
fix(fixer) {
return fixer.removeRange([lastTokens[0].range[1], lastTokens[1].range[0]]);
},
});
}
} else if (option === 'always' && adjacent) {
report(context, messages.selfCloseSlashNeedSpace, 'selfCloseSlashNeedSpace', {
node,
loc: {
start: lastTokens[0].loc.start,
end: lastTokens[1].loc.end,
},
fix(fixer) {
return fixer.insertTextBefore(lastTokens[1], ' ');
},
});
}
} else {
const firstTokens = sourceCode.getFirstTokens(node, 2);
adjacent = !sourceCode.isSpaceBetweenTokens(firstTokens[0], firstTokens[1]);
if (option === 'never') {
if (!adjacent) {
report(context, messages.closeSlashNoSpace, 'closeSlashNoSpace', {
node,
loc: {
start: firstTokens[0].loc.start,
end: firstTokens[1].loc.end,
},
fix(fixer) {
return fixer.removeRange([firstTokens[0].range[1], firstTokens[1].range[0]]);
},
});
}
} else if (option === 'always' && adjacent) {
report(context, messages.closeSlashNeedSpace, 'closeSlashNeedSpace', {
node,
loc: {
start: firstTokens[0].loc.start,
end: firstTokens[1].loc.end,
},
fix(fixer) {
return fixer.insertTextBefore(firstTokens[1], ' ');
},
});
}
}
}
function validateBeforeSelfClosing(context, node, option) {
const sourceCode = context.getSourceCode();
const leftToken = getTokenBeforeClosingBracket(node);
const closingSlash = sourceCode.getTokenAfter(leftToken);
if (node.loc.start.line !== node.loc.end.line && option === 'proportional-always') {
if (leftToken.loc.end.line === closingSlash.loc.start.line) {
report(context, messages.beforeSelfCloseNeedNewline, 'beforeSelfCloseNeedNewline', {
node,
loc: leftToken.loc.end,
fix(fixer) {
return fixer.insertTextBefore(closingSlash, '\n');
},
});
return;
}
}
if (leftToken.loc.end.line !== closingSlash.loc.start.line) {
return;
}
const adjacent = !sourceCode.isSpaceBetweenTokens(leftToken, closingSlash);
if ((option === 'always' || option === 'proportional-always') && adjacent) {
report(context, messages.beforeSelfCloseNeedSpace, 'beforeSelfCloseNeedSpace', {
node,
loc: closingSlash.loc.start,
fix(fixer) {
return fixer.insertTextBefore(closingSlash, ' ');
},
});
} else if (option === 'never' && !adjacent) {
report(context, messages.beforeSelfCloseNoSpace, 'beforeSelfCloseNoSpace', {
node,
loc: closingSlash.loc.start,
fix(fixer) {
const previousToken = sourceCode.getTokenBefore(closingSlash);
return fixer.removeRange([previousToken.range[1], closingSlash.range[0]]);
},
});
}
}
function validateAfterOpening(context, node, option) {
const sourceCode = context.getSourceCode();
const openingToken = sourceCode.getTokenBefore(node.name);
if (option === 'allow-multiline') {
if (openingToken.loc.start.line !== node.name.loc.start.line) {
return;
}
}
const adjacent = !sourceCode.isSpaceBetweenTokens(openingToken, node.name);
if (option === 'never' || option === 'allow-multiline') {
if (!adjacent) {
report(context, messages.afterOpenNoSpace, 'afterOpenNoSpace', {
node,
loc: {
start: openingToken.loc.start,
end: node.name.loc.start,
},
fix(fixer) {
return fixer.removeRange([openingToken.range[1], node.name.range[0]]);
},
});
}
} else if (option === 'always' && adjacent) {
report(context, messages.afterOpenNeedSpace, 'afterOpenNeedSpace', {
node,
loc: {
start: openingToken.loc.start,
end: node.name.loc.start,
},
fix(fixer) {
return fixer.insertTextBefore(node.name, ' ');
},
});
}
}
function validateBeforeClosing(context, node, option) {
// Don't enforce this rule for self closing tags
if (!node.selfClosing) {
const sourceCode = context.getSourceCode();
const leftToken = option === 'proportional-always'
? getTokenBeforeClosingBracket(node)
: sourceCode.getLastTokens(node, 2)[0];
const closingToken = sourceCode.getTokenAfter(leftToken);
if (node.loc.start.line !== node.loc.end.line && option === 'proportional-always') {
if (leftToken.loc.end.line === closingToken.loc.start.line) {
report(context, messages.beforeCloseNeedNewline, 'beforeCloseNeedNewline', {
node,
loc: leftToken.loc.end,
fix(fixer) {
return fixer.insertTextBefore(closingToken, '\n');
},
});
return;
}
}
if (leftToken.loc.start.line !== closingToken.loc.start.line) {
return;
}
const adjacent = !sourceCode.isSpaceBetweenTokens(leftToken, closingToken);
if (option === 'never' && !adjacent) {
report(context, messages.beforeCloseNoSpace, 'beforeCloseNoSpace', {
node,
loc: {
start: leftToken.loc.end,
end: closingToken.loc.start,
},
fix(fixer) {
return fixer.removeRange([leftToken.range[1], closingToken.range[0]]);
},
});
} else if (option === 'always' && adjacent) {
report(context, messages.beforeCloseNeedSpace, 'beforeCloseNeedSpace', {
node,
loc: {
start: leftToken.loc.end,
end: closingToken.loc.start,
},
fix(fixer) {
return fixer.insertTextBefore(closingToken, ' ');
},
});
} else if (option === 'proportional-always' && node.type === 'JSXOpeningElement' && adjacent !== (node.loc.start.line === node.loc.end.line)) {
report(context, messages.beforeCloseNeedSpace, 'beforeCloseNeedSpace', {
node,
loc: {
start: leftToken.loc.end,
end: closingToken.loc.start,
},
fix(fixer) {
return fixer.insertTextBefore(closingToken, ' ');
},
});
}
}
}
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const optionDefaults = {
closingSlash: 'never',
beforeSelfClosing: 'always',
afterOpening: 'never',
beforeClosing: 'allow',
};
module.exports = {
meta: {
docs: {
description: 'Enforce whitespace in and around the JSX opening and closing brackets',
category: 'Stylistic Issues',
recommended: false,
url: docsUrl('jsx-tag-spacing'),
},
fixable: 'whitespace',
messages,
schema: [
{
type: 'object',
properties: {
closingSlash: {
enum: ['always', 'never', 'allow'],
},
beforeSelfClosing: {
enum: ['always', 'proportional-always', 'never', 'allow'],
},
afterOpening: {
enum: ['always', 'allow-multiline', 'never', 'allow'],
},
beforeClosing: {
enum: ['always', 'proportional-always', 'never', 'allow'],
},
},
default: optionDefaults,
additionalProperties: false,
},
],
},
create(context) {
const options = Object.assign({}, optionDefaults, context.options[0]);
return {
JSXOpeningElement(node) {
if (options.closingSlash !== 'allow' && node.selfClosing) {
validateClosingSlash(context, node, options.closingSlash);
}
if (options.afterOpening !== 'allow') {
validateAfterOpening(context, node, options.afterOpening);
}
if (options.beforeSelfClosing !== 'allow' && node.selfClosing) {
validateBeforeSelfClosing(context, node, options.beforeSelfClosing);
}
if (options.beforeClosing !== 'allow') {
validateBeforeClosing(context, node, options.beforeClosing);
}
},
JSXClosingElement(node) {
if (options.afterOpening !== 'allow') {
validateAfterOpening(context, node, options.afterOpening);
}
if (options.closingSlash !== 'allow') {
validateClosingSlash(context, node, options.closingSlash);
}
if (options.beforeClosing !== 'allow') {
validateBeforeClosing(context, node, options.beforeClosing);
}
},
};
},
};