Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/parsers/angular.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,10 +284,15 @@ function createLiteralsByAngularBoundAttributeName(ctx: Rule.RuleContext, attrib
const line = ctx.sourceCode.lines[loc.start.line - 1];
const indentation = getIndentation(line);
const supportsMultiline = false;
const concatenation = {
isConcatenatedLeft: false,
isConcatenatedRight: false
};

return [{
...quotes,
...whitespaces,
...concatenation,
content,
indentation,
loc,
Expand Down Expand Up @@ -332,10 +337,15 @@ function createLiteralByLiteralMapKey(ctx: Rule.RuleContext, key: LiteralMapProp
const loc = getLocByRange(ctx, range);
const line = ctx.sourceCode.lines[loc.start.line - 1] ?? "";
const indentation = getIndentation(line);
const concatenation = {
isConcatenatedLeft: false,
isConcatenatedRight: false
};

return [{
...quotes,
...whitespaces,
...concatenation,
content: keyContent,
indentation,
loc,
Expand Down Expand Up @@ -363,10 +373,15 @@ function createLiteralsByAngularTextAttribute(ctx: Rule.RuleContext, attribute:
const line = ctx.sourceCode.lines[loc.start.line - 1];
const indentation = getIndentation(line);
const supportsMultiline = true;
const concatenation = {
isConcatenatedLeft: false,
isConcatenatedRight: false
};

return [{
...quotes,
...whitespaces,
...concatenation,
content,
indentation,
loc,
Expand All @@ -393,11 +408,13 @@ function createLiteralByAngularLiteralPrimitive(ctx: Rule.RuleContext, literal:
const loc = getLocByRange(ctx, range);
const line = ctx.sourceCode.lines[loc.start.line - 1];
const indentation = getIndentation(line);
const concatenation = getStringConcatenationMeta(ctx, literal);
const supportsMultiline = true;

return [{
...quotes,
...whitespaces,
...concatenation,
content,
indentation,
loc,
Expand Down Expand Up @@ -433,11 +450,13 @@ function createLiteralByAngularTemplateLiteralElement(ctx: Rule.RuleContext, lit
const parentLine = ctx.sourceCode.lines[parentLoc.start.line - 1];
const indentation = getIndentation(parentLine);
const supportsMultiline = true;
const concatenation = getStringConcatenationMeta(ctx, literal);

return [{
...quotes,
...whitespaces,
...braces,
...concatenation,
content,
indentation,
isInterpolated,
Expand Down Expand Up @@ -534,6 +553,27 @@ function isInsideLogicalExpressionLeft(ctx: Rule.RuleContext, ast: AST): boolean
return isInsideConditionalExpressionCondition(ctx, parent);
}

function getStringConcatenationMeta(ctx: Rule.RuleContext, ast: AST, isConcatenatedLeft = false, isConcatenatedRight = false): { isConcatenatedLeft: boolean; isConcatenatedRight: boolean; } {
const parent = findParent(ctx, ast);
if(!parent){
return {
isConcatenatedLeft,
isConcatenatedRight
};
}

if(isBinary(parent) && parent.operation === "+"){
return getStringConcatenationMeta(
ctx,
parent,
isConcatenatedLeft || parent.right === ast,
isConcatenatedRight || parent.left === ast
);
}

return getStringConcatenationMeta(ctx, parent, isConcatenatedLeft, isConcatenatedRight);
}

function isInsideObjectValue(ctx: Rule.RuleContext, ast: AST): boolean {
const parent = findParent(ctx, ast);
if(!parent){ return false; }
Expand Down
2 changes: 1 addition & 1 deletion src/parsers/es.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ describe("es", () => {
});

// #234
it("should ignore literals in binary expressions", () => {
it("should ignore literals in binary comparisons", () => {
lint(noUnnecessaryWhitespace, {
valid: [
{
Expand Down
33 changes: 29 additions & 4 deletions src/parsers/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import {
findMatchingParentNodes,
getLiteralNodesByMatchers,
isIndexedAccessLiteral,
isInsideBinaryExpression,
isInsideConditionalExpressionTest,
isInsideDisallowedBinaryExpression,
isInsideLogicalExpressionLeft,
isInsideMemberExpression,
matchesPathPattern
Expand Down Expand Up @@ -197,11 +197,13 @@ export function getStringLiteralByESStringLiteral(ctx: Rule.RuleContext, node: E
const indentation = getIndentation(line);
const multilineQuotes = getMultilineQuotes(node);
const supportsMultiline = !isESObjectKey(node);
const concatenation = getStringConcatenationMeta(node);

return {
...quotes,
...whitespaces,
...multilineQuotes,
...concatenation,
content,
indentation,
isInterpolated: false,
Expand Down Expand Up @@ -233,12 +235,14 @@ function getLiteralByESTemplateElement(ctx: Rule.RuleContext, node: ESTemplateEl
const whitespaces = getWhitespace(content);
const indentation = getIndentation(line);
const multilineQuotes = getMultilineQuotes(node);
const concatenation = getStringConcatenationMeta(node);

return {
...whitespaces,
...quotes,
...braces,
...multilineQuotes,
...concatenation,
content,
indentation,
isInterpolated,
Expand Down Expand Up @@ -609,6 +613,27 @@ function getIsInterpolated(ctx: Rule.RuleContext, raw: string): boolean {
return !!braces.closingBraces || !!braces.openingBraces;
}

function getStringConcatenationMeta(node: ESNode, isConcatenatedLeft = false, isConcatenatedRight = false): { isConcatenatedLeft: boolean; isConcatenatedRight: boolean; } {
if(!hasESNodeParentExtension(node)){
return {
isConcatenatedLeft,
isConcatenatedRight
};
}

const parent = node.parent;

if(parent.type === "BinaryExpression" && parent.operator === "+"){
return getStringConcatenationMeta(
parent,
isConcatenatedLeft || parent.right === node,
isConcatenatedRight || parent.left === node
);
}

return getStringConcatenationMeta(parent, isConcatenatedLeft, isConcatenatedRight);
}

function getESMatcherFunctions(matchers: SelectorMatcher[]): MatcherFunctions<ESNode> {
return matchers.reduce<MatcherFunctions<ESNode>>((matcherFunctions, matcher) => {
switch (matcher.type){
Expand All @@ -619,7 +644,7 @@ function getESMatcherFunctions(matchers: SelectorMatcher[]): MatcherFunctions<ES
!isESNode(node) ||
!hasESNodeParentExtension(node) ||

isInsideBinaryExpression(node) ||
isInsideDisallowedBinaryExpression(node) ||
isInsideConditionalExpressionTest(node) ||
isInsideLogicalExpressionLeft(node) ||
isIndexedAccessLiteral(node) ||
Expand All @@ -641,7 +666,7 @@ function getESMatcherFunctions(matchers: SelectorMatcher[]): MatcherFunctions<ES
!hasESNodeParentExtension(node) ||
!isESObjectKey(node) ||

isInsideBinaryExpression(node) ||
isInsideDisallowedBinaryExpression(node) ||
isInsideConditionalExpressionTest(node) ||
isInsideLogicalExpressionLeft(node) ||
isInsideMemberExpression(node) ||
Expand All @@ -667,7 +692,7 @@ function getESMatcherFunctions(matchers: SelectorMatcher[]): MatcherFunctions<ES
!hasESNodeParentExtension(node) ||
!isInsideObjectValue(node) ||

isInsideBinaryExpression(node) ||
isInsideDisallowedBinaryExpression(node) ||
isInsideConditionalExpressionTest(node) ||
isInsideLogicalExpressionLeft(node) ||
isESObjectKey(node) ||
Expand Down
8 changes: 4 additions & 4 deletions src/parsers/svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { MatcherType } from "better-tailwindcss:types/rule.js";
import {
getLiteralNodesByMatchers,
isIndexedAccessLiteral,
isInsideBinaryExpression,
isInsideConditionalExpressionTest,
isInsideDisallowedBinaryExpression,
isInsideLogicalExpressionLeft,
isInsideMemberExpression,
matchesPathPattern
Expand Down Expand Up @@ -321,7 +321,7 @@ function getSvelteMatcherFunctions(matchers: SelectorMatcher[]): MatcherFunction
!isESNode(node) ||
!hasESNodeParentExtension(node) ||

isInsideBinaryExpression(node) ||
isInsideDisallowedBinaryExpression(node) ||
isInsideConditionalExpressionTest(node) ||
isInsideLogicalExpressionLeft(node) ||
isIndexedAccessLiteral(node) ||
Expand All @@ -344,7 +344,7 @@ function getSvelteMatcherFunctions(matchers: SelectorMatcher[]): MatcherFunction
!hasESNodeParentExtension(node) ||
!isESObjectKey(node) ||

isInsideBinaryExpression(node) ||
isInsideDisallowedBinaryExpression(node) ||
isInsideConditionalExpressionTest(node) ||
isInsideLogicalExpressionLeft(node) ||
isInsideMemberExpression(node) ||
Expand All @@ -370,7 +370,7 @@ function getSvelteMatcherFunctions(matchers: SelectorMatcher[]): MatcherFunction
!hasESNodeParentExtension(node) ||
!isInsideObjectValue(node) ||

isInsideBinaryExpression(node) ||
isInsideDisallowedBinaryExpression(node) ||
isInsideConditionalExpressionTest(node) ||
isInsideLogicalExpressionLeft(node) ||
isESObjectKey(node) ||
Expand Down
8 changes: 4 additions & 4 deletions src/parsers/vue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import { MatcherType } from "better-tailwindcss:types/rule.js";
import {
getLiteralNodesByMatchers,
isIndexedAccessLiteral,
isInsideBinaryExpression,
isInsideConditionalExpressionTest,
isInsideDisallowedBinaryExpression,
isInsideLogicalExpressionLeft,
isInsideMemberExpression,
matchesPathPattern
Expand Down Expand Up @@ -192,7 +192,7 @@ function getVueMatcherFunctions(matchers: SelectorMatcher[]): MatcherFunctions<E
!isESNode(node) ||
!hasESNodeParentExtension(node) ||

isInsideBinaryExpression(node) ||
isInsideDisallowedBinaryExpression(node) ||
isInsideConditionalExpressionTest(node) ||
isInsideLogicalExpressionLeft(node) ||
isIndexedAccessLiteral(node) ||
Expand All @@ -214,7 +214,7 @@ function getVueMatcherFunctions(matchers: SelectorMatcher[]): MatcherFunctions<E
!hasESNodeParentExtension(node) ||
!isESObjectKey(node) ||

isInsideBinaryExpression(node) ||
isInsideDisallowedBinaryExpression(node) ||
isInsideConditionalExpressionTest(node) ||
isInsideLogicalExpressionLeft(node) ||
isInsideMemberExpression(node) ||
Expand All @@ -240,7 +240,7 @@ function getVueMatcherFunctions(matchers: SelectorMatcher[]): MatcherFunctions<E
!hasESNodeParentExtension(node) ||
!isInsideObjectValue(node) ||

isInsideBinaryExpression(node) ||
isInsideDisallowedBinaryExpression(node) ||
isInsideConditionalExpressionTest(node) ||
isInsideLogicalExpressionLeft(node) ||
isESObjectKey(node) ||
Expand Down
99 changes: 99 additions & 0 deletions src/rules/no-unnecessary-whitespace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,105 @@ describe(noUnnecessaryWhitespace.name, () => {
});
});

it("should trim unnecessary whitespace in concatenated strings", () => {
lint(noUnnecessaryWhitespace, {
invalid: [
{
angular: `<img [class]="' a ' + ' b '" />`,
angularOutput: `<img [class]="'a ' + ' b'" />`,
jsx: `() => <img class={" a " + " b "} />`,
jsxOutput: `() => <img class={"a " + " b"} />`,
svelte: `<img class={" a " + " b "} />`,
svelteOutput: `<img class={"a " + " b"} />`,
vue: `<template><img :class="' a ' + ' b '" /></template>`,
vueOutput: `<template><img :class="'a ' + ' b'" /></template>`,

errors: 4
},
{
angular: `<img [class]="' a ' + ' b ' + ' c '" />`,
angularOutput: `<img [class]="'a ' + ' b ' + ' c'" />`,
jsx: `() => <img class={" a " + " b " + " c "} />`,
jsxOutput: `() => <img class={"a " + " b " + " c"} />`,
svelte: `<img class={" a " + " b " + " c "} />`,
svelteOutput: `<img class={"a " + " b " + " c"} />`,
vue: `<template><img :class="' a ' + ' b ' + ' c '" /></template>`,
vueOutput: `<template><img :class="'a ' + ' b ' + ' c'" /></template>`,

errors: 6
}
],
valid: [
{
angular: `<img [class]="'a ' + ' b'" />`,
jsx: `() => <img class={"a " + " b"} />`,
svelte: `<img class={"a " + " b"} />`,
vue: `<template><img :class="'a ' + ' b'" /></template>`
},
{
angular: `<img [class]="'a ' + ' b ' + ' c'" />`,
jsx: `() => <img class={"a " + " b " + " c"} />`,
svelte: `<img class={"a " + " b " + " c"} />`,
vue: `<template><img :class="'a ' + ' b ' + ' c'" /></template>`
}
]
});
});

it("should trim unnecessary whitespace in conditionally concatenated strings", () => {
lint(noUnnecessaryWhitespace, {
invalid: [
{
angular: `<img [class]="' a ' + (someVar ? ' b ' : ' c ')" />`,
angularOutput: `<img [class]="'a ' + (someVar ? ' b' : ' c')" />`,
jsx: `() => <img class={" a " + (someVar ? " b " : " c ")} />`,
jsxOutput: `() => <img class={"a " + (someVar ? " b" : " c")} />`,
svelte: `<img class={" a " + (someVar ? " b " : " c ")} />`,
svelteOutput: `<img class={"a " + (someVar ? " b" : " c")} />`,
vue: `<template><img :class="' a ' + (someVar ? ' b ' : ' c ')" /></template>`,
vueOutput: `<template><img :class="'a ' + (someVar ? ' b' : ' c')" /></template>`,

errors: 6
}
],
valid: [
{
angular: `<img [class]="'a ' + (someVar ? ' b' : ' c')" />`,
jsx: `() => <img class={"a " + (someVar ? " b" : " c")} />`,
svelte: `<img class={"a " + (someVar ? " b" : " c")} />`,
vue: `<template><img :class="'a ' + (someVar ? ' b' : ' c')" /></template>`
}
]
});
});

it("should trim unnecessary whitespace in conditionally concatenated template literal strings", () => {
lint(noUnnecessaryWhitespace, {
invalid: [
{
angular: '<img [class]="` a ` + (someVar ? ` b ` : ` c `)" />',
angularOutput: '<img [class]="`a ` + (someVar ? ` b` : ` c`)" />',
jsx: "() => <img class={` a ` + (someVar ? ` b ` : ` c `)} />",
jsxOutput: "() => <img class={`a ` + (someVar ? ` b` : ` c`)} />",
svelte: "<img class={` a ` + (someVar ? ` b ` : ` c `)} />",
svelteOutput: "<img class={`a ` + (someVar ? ` b` : ` c`)} />",
vue: '<template><img :class="` a ` + (someVar ? ` b ` : ` c `)" /></template>',
vueOutput: '<template><img :class="`a ` + (someVar ? ` b` : ` c`)" /></template>',

errors: 6
}
],
valid: [
{
angular: '<img [class]="`a ` + (someVar ? ` b` : ` c`)" />',
jsx: "() => <img class={`a ` + (someVar ? ` b` : ` c`)} />",
svelte: "<img class={`a ` + (someVar ? ` b` : ` c`)} />",
vue: '<template><img :class="`a ` + (someVar ? ` b` : ` c`)" /></template>'
}
]
});
});
Comment thread
schoero marked this conversation as resolved.

it("should remove whitespace in empty strings", () => {
lint(noUnnecessaryWhitespace, {
invalid: [
Expand Down
Loading