Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extend genericSpacing to cover funtion annotations #25

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion src/configs/recommended.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,4 @@
"onlyFilesWithFlowAnnotation": false
}
}
}
}
197 changes: 197 additions & 0 deletions src/rules/genericSpacing.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,209 @@ const schema = [
},
];

function isNeverOption(context) {
return (context.options[0] || 'never') === 'never';
}

function isWhitespaceCRLF(whitespace) {
return whitespace !== '\n' && whitespace !== '\r';
}

function spacesOutside(node, context) {
const { callee, typeArguments } = node;
if (typeArguments == null) {
return;
}

const sourceCode = context.getSourceCode();
const { name } = callee;
const never = isNeverOption(context);
const parentheses = sourceCode.getTokenAfter(typeArguments);

const spacesBefore = typeArguments.range[0] - callee.range[1];
const spacesAfter = parentheses.range[0] - typeArguments.range[1];

if (never) {
if (spacesBefore) {
const whiteSpaceBefore = sourceCode.text[typeArguments.range[0]];

if (isWhitespaceCRLF(whiteSpaceBefore)) {
context.report({
data: { name },
fix: spacingFixers.stripSpacesBefore(typeArguments, spacesBefore),
message: 'There must be no space before "{{name}}" type annotation',
node,
});
}
}

if (spacesAfter) {
const whiteSpaceAfter = sourceCode.text[typeArguments.range[1] - 1];

if (isWhitespaceCRLF(whiteSpaceAfter)) {
context.report({
data: { name },
fix: spacingFixers.stripSpacesAfter(typeArguments, spacesAfter),
message: 'There must be no space after "{{name}}" type annotation',
node,
});
}
}

return;
}

if (!never) {
if (spacesBefore > 1) {
context.report({
data: { name },
fix: spacingFixers.stripSpacesBefore(typeArguments, spacesBefore - 1),
message: 'There must be one space before "{{name}}" generic type annotation bracket',
node,
});
}

if (spacesBefore === 0) {
context.report({
data: { name },
fix: spacingFixers.addSpaceBefore(typeArguments),
message: 'There must be a space before "{{name}}" generic type annotation bracket',
node,
});
}

if (spacesAfter > 1) {
context.report({
data: { name },
fix: spacingFixers.stripSpacesAfter(typeArguments, spacesAfter),
message: 'There must be one space before "{{name}}" generic type annotation bracket',
node,
});
}

if (spacesAfter === 0) {
context.report({
data: { name },
fix: spacingFixers.addSpaceAfter(typeArguments),
message: 'There must be a space before "{{name}}" generic type annotation bracket',
node,
});
}
}
}

function spacesInside(node, context) {
const { callee, typeArguments } = node;
if (typeArguments == null) {
return;
}

const sourceCode = context.getSourceCode();
const { name } = callee;
const never = isNeverOption(context);
const isNullable = typeArguments.params[0].type === 'NullableTypeAnnotation';
const [
opener,
firstInnerToken,
secondInnerToken,
] = sourceCode.getFirstTokens(typeArguments, 3);
const [
lastInnerToken,
closer,
] = sourceCode.getLastTokens(typeArguments, 2);

const spacesBefore = firstInnerToken.range[0] - opener.range[1];
const spaceBetweenNullToken = secondInnerToken.range[0] - firstInnerToken.range[1];
const spacesAfter = closer.range[0] - lastInnerToken.range[1];

if (never) {
if (spacesBefore) {
const whiteSpaceBefore = sourceCode.text[opener.range[1]];

if (whiteSpaceBefore !== '\n' && whiteSpaceBefore !== '\r') {
context.report({
data: { name },
fix: spacingFixers.stripSpacesAfter(opener, spacesBefore),
message: 'There must be no spaces inside at the start of "{{name}}" type annotation',
node,
});
}
}

if (isNullable && spaceBetweenNullToken) {
context.report({
data: { name },
fix: spacingFixers.stripSpacesAfter(firstInnerToken, spaceBetweenNullToken),
message: 'There must be no spaces inside "{{name}}" type annotation',
node,
});
}

if (spacesAfter) {
const whiteSpaceAfter = sourceCode.text[closer.range[0] - 1];

if (isWhitespaceCRLF(whiteSpaceAfter)) {
context.report({
data: { name },
fix: spacingFixers.stripSpacesAfter(lastInnerToken, spacesAfter),
message: 'There must be no spaces inside at the end of "{{name}}" type annotation',
node,
});
}
}

return;
}

if (!never) {
if (spacesBefore > 1) {
context.report({
data: { name },
fix: spacingFixers.stripSpacesBefore(opener, spacesBefore - 1),
message: 'There must be one space before "{{name}}" generic type annotation bracket',
node,
});
}

if (spacesBefore === 0) {
context.report({
data: { name },
fix: spacingFixers.addSpaceBefore(opener),
message: 'There must be a space before "{{name}}" generic type annotation bracket',
node,
});
}

if (spacesAfter > 1) {
context.report({
data: { name },
fix: spacingFixers.stripSpacesAfter(closer, spacesAfter),
message: 'There must be one space before "{{name}}" generic type annotation bracket',
node,
});
}

if (spacesAfter === 0) {
context.report({
data: { name },
fix: spacingFixers.addSpaceAfter(closer),
message: 'There must be a space before "{{name}}" generic type annotation bracket',
node,
});
}
}
}

const create = (context) => {
const sourceCode = context.getSourceCode();

const never = (context.options[0] || 'never') === 'never';

return {
CallExpression(node) {
spacesOutside(node, context);
spacesInside(node, context);
},
GenericTypeAnnotation(node) {
const types = node.typeParameters;

Expand Down
103 changes: 101 additions & 2 deletions tests/rules/assertions/genericSpacing.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export default {
invalid: [
// Never

{
code: 'type X = Promise< string>',
errors: [{ message: 'There must be no space at start of "Promise" generic type annotation' }],
Expand Down Expand Up @@ -91,6 +90,68 @@ export default {
options: ['always'],
output: 'type X = Promise< (foo), bar, (((baz))) >',
},

// Type annotations
{
code: 'const [state, setState] = useState<?string >(null)',
errors: [{ message: 'There must be no space at start of type annotations' }],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
errors: [{ message: 'There must be no space at start of type annotations' }],
errors: [{ message: 'There must be no space at end of type annotations' }],

output: 'const [state, setState] = useState<?string>(null)',
},
{
code: 'const [state, setState] = useState<?string > (null)',
errors: [{ message: 'There must be no space at start of type annotations' }],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
errors: [{ message: 'There must be no space at start of type annotations' }],
errors: [{ message: 'There must be no space at end of type annotations' }],

output: 'const [state, setState] = useState<?string>(null)',
},
{
code: 'const [state, setState] = useState< ?string>(null)',
errors: [{ message: 'There must be no space at start of type annotations' }],
output: 'const [state, setState] = useState<?string>(null)',
},
{
code: 'const [state, setState] = useState < ?string>(null)',
errors: [{ message: 'There must be no space at start of type annotations' }],
output: 'const [state, setState] = useState<?string>(null)',
},
{
code: 'const [state, setState] = useState<? string>(null)',
errors: [{ message: 'There must be no space at start of type annotations' }],
output: 'const [state, setState] = useState<?string>(null)',
},
{
code: 'const [state, setState] = useState< ? string>(null)',
errors: [{ message: 'There must be no space at start of type annotations' }],
output: 'const [state, setState] = useState<?string>(null)',
},
{
code: 'const [state, setState] = useState< ? string >(null)',
errors: [{ message: 'There must be no space at start of type annotations' }],
output: 'const [state, setState] = useState<?string>(null)',
},
{
code: 'const [state, setState] = useState < ? string > (null)',
errors: [{ message: 'There must be no space at start of type annotations' }],
output: 'const [state, setState] = useState<?string>(null)',
},
{
code: 'const [state, setState] = useState < ? string > ()',
errors: [{ message: 'There must be no space at start of type annotations' }],
output: 'const [state, setState] = useState<?string>(null)',
},
{
code: 'useState<string >(null)',
errors: [{ message: 'There must be no space at start of type annotations' }],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should say end of type annotations

output: 'useState<string>(null)',
},
{
code: 'useState< string>(null)',
errors: [{ message: 'There must be no space at start of type annotations' }],
output: 'useState<string>(null)',
},
{
code: 'useState< string >(null)',
errors: [{ message: 'There must be no space at start of type annotations' }],
output: 'useState<string>(null)',
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test that's something like the following if it doesn't already exist elsewhere I'm interested to know if it strips all spaces blindly or only when it's between maybe types ? and arrow brackets </>

    {
      code: 'useState< string | number >(null)',
      errors: [{ message: 'There must be no space at start of type annotations' }],
      output: 'useState<string | number>(null)',
    },

],
misconfigured: [
{
Expand Down Expand Up @@ -131,7 +192,7 @@ export default {
{ code: 'type X = Promise<(foo), bar, (((baz)))>' },
{
code:
`type X = Promise<
`type X = Promise<
(foo),
bar,
(((baz)))
Expand All @@ -153,5 +214,43 @@ export default {
code: 'type X = Promise< (foo), bar, (((baz))) >',
options: ['always'],
},
{
code: 'const [state, setState] = useState< string >("")',
options: ['always'],
},
{
code: 'const [state, setState] = useState< ?string >(null)',
options: ['always'],
},
{
code: 'const [state, setState] = useState< string | null >(null)',
options: ['always'],
},
{
code: 'const [state, setState] = useState< string | number >(2)',
options: ['always'],
},

// Never
{
code: 'const [state, setState] = useState(null)',
options: ['never'],
},
{
code: 'const [state, setState] = useState<string>("")',
options: ['never'],
},
{
code: 'const [state, setState] = useState<?string>(null)',
options: ['never'],
},
{
code: 'const [state, setState] = useState<string | null>(null)',
options: ['never'],
},
{
code: 'const [state, setState] = useState<string | number>(2)',
options: ['never'],
},
],
};