Skip to content

Commit 7b12bbf

Browse files
author
Dimitri POSTOLOV
authored
fix(no-hashtag-description): allow hashtag comments between fields and arguments (#588)
1 parent 2032a66 commit 7b12bbf

File tree

7 files changed

+114
-141
lines changed

7 files changed

+114
-141
lines changed

.changeset/two-bees-chew.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-eslint/eslint-plugin': patch
3+
---
4+
5+
fix(no-hashtag-description): allow hashtag comments between fields and arguments

packages/plugin/src/estree-parser/converter.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@ export function convertToESTree<T extends ASTNode>(
77
node: T,
88
typeInfo?: TypeInfo
99
): { rootTree: GraphQLESTreeNode<T>; comments: Comment[] } {
10-
const comments = extractCommentsFromAst(node);
1110
const visitor = { leave: convertNode(typeInfo) };
12-
1311
return {
1412
rootTree: visit(node, typeInfo ? visitWithTypeInfo(typeInfo, visitor) : visitor),
15-
comments,
13+
comments: extractCommentsFromAst(node.loc),
1614
};
1715
}
1816

packages/plugin/src/estree-parser/utils.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,33 +69,28 @@ export function convertRange(gqlLocation: Location): [number, number] {
6969
return [gqlLocation.start, gqlLocation.end];
7070
}
7171

72-
export function extractCommentsFromAst(node: ASTNode): Comment[] {
73-
const { loc } = node;
74-
72+
export function extractCommentsFromAst(loc: Location): Comment[] {
7573
if (!loc) {
7674
return [];
7775
}
78-
7976
const comments: Comment[] = [];
8077
let token = loc.startToken;
8178

8279
while (token !== null) {
83-
if (token.kind === TokenKind.COMMENT) {
84-
const { line, column } = token;
80+
const { kind, value, line, column, start, end, next } = token;
81+
if (kind === TokenKind.COMMENT) {
8582
comments.push({
8683
type: 'Block',
87-
value: ` ${token.value} `,
84+
value,
8885
loc: {
8986
start: { line, column },
9087
end: { line, column },
9188
},
92-
range: [token.start, token.end],
89+
range: [start, end],
9390
});
9491
}
95-
96-
token = token.next;
92+
token = next;
9793
}
98-
9994
return comments;
10095
}
10196

packages/plugin/src/rules/no-hashtag-description.ts

Lines changed: 20 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
import { GraphQLESLintRule } from '../types';
21
import { TokenKind } from 'graphql';
3-
import { checkForEslint } from '../utils';
2+
import { GraphQLESLintRule } from '../types';
43

54
const HASHTAG_COMMENT = 'HASHTAG_COMMENT';
65

76
const rule: GraphQLESLintRule = {
87
meta: {
98
messages: {
10-
[HASHTAG_COMMENT]: `Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.`,
9+
[HASHTAG_COMMENT]:
10+
'Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.',
1111
},
1212
docs: {
1313
description:
1414
'Requires to use `"""` or `"` for adding a GraphQL description instead of `#`.\nAllows to use hashtag for comments, as long as it\'s not attached to an AST definition.',
1515
category: 'Best Practices',
16-
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-hashtag-description.md`,
16+
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-hashtag-description.md',
1717
examples: [
1818
{
1919
title: 'Incorrect',
@@ -55,60 +55,27 @@ const rule: GraphQLESLintRule = {
5555
create(context) {
5656
return {
5757
Document(node) {
58-
if (node) {
59-
const rawNode = node.rawNode();
58+
const rawNode = node.rawNode();
59+
let token = rawNode.loc.startToken;
6060

61-
if (rawNode && rawNode.loc && rawNode.loc.startToken) {
62-
let token = rawNode.loc.startToken;
61+
while (token !== null) {
62+
const { kind, prev, next, value, line, column } = token;
6363

64-
while (token !== null) {
65-
if (token.kind === TokenKind.COMMENT && token.next && token.prev) {
66-
if (
67-
token.prev.kind !== TokenKind.SOF &&
68-
token.prev.kind !== TokenKind.COMMENT &&
69-
token.next.kind !== TokenKind.COMMENT &&
70-
token.next.line - token.line > 1 &&
71-
token.prev.line !== token.line
72-
) {
73-
context.report({
74-
messageId: HASHTAG_COMMENT,
75-
loc: {
76-
start: {
77-
line: token.line,
78-
column: token.column,
79-
},
80-
end: {
81-
line: token.line,
82-
column: token.column,
83-
},
84-
},
85-
});
86-
} else if (
87-
token.next.kind !== TokenKind.COMMENT &&
88-
!checkForEslint(token, rawNode) &&
89-
token.next.kind !== TokenKind.EOF &&
90-
token.next.line - token.line < 2 &&
91-
token.prev.line !== token.line
92-
) {
93-
context.report({
94-
messageId: HASHTAG_COMMENT,
95-
loc: {
96-
start: {
97-
line: token.line,
98-
column: token.column,
99-
},
100-
end: {
101-
line: token.line,
102-
column: token.column,
103-
},
104-
},
105-
});
106-
}
107-
}
64+
if (kind === TokenKind.COMMENT && prev && next) {
65+
const isEslintComment = value.trimLeft().startsWith('eslint');
66+
const linesAfter = next.line - line;
10867

109-
token = token.next;
68+
if (!isEslintComment && line !== prev.line && next.kind === TokenKind.NAME && linesAfter < 2) {
69+
context.report({
70+
messageId: HASHTAG_COMMENT,
71+
loc: {
72+
start: { line, column },
73+
end: { line, column },
74+
},
75+
});
11076
}
11177
}
78+
token = next;
11279
}
11380
},
11481
};

packages/plugin/src/testkit.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class GraphQLRuleTester extends require('eslint').RuleTester {
3939
name: string,
4040
rule: GraphQLESLintRule,
4141
tests: {
42-
valid: GraphQLValidTestCase<Config>[];
42+
valid: (string | GraphQLValidTestCase<Config>)[];
4343
invalid: GraphQLInvalidTestCase<Config>[];
4444
}
4545
): void {

packages/plugin/src/utils.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -102,17 +102,6 @@ export function extractTokens(source: string): AST.Token[] {
102102
return tokens;
103103
}
104104

105-
export function checkForEslint(token: Token, rawNode: DocumentNode): boolean {
106-
if (token.kind !== 'Comment') {
107-
return false;
108-
}
109-
const string = rawNode.loc?.source.body.substring(token.start + 1, token.start + 8);
110-
if (string.toLocaleLowerCase().includes('eslint')) {
111-
return true;
112-
}
113-
return false;
114-
}
115-
116105
export const normalizePath = (path: string): string => (path || '').replace(/\\/g, '/');
117106

118107
/**
@@ -152,4 +141,4 @@ export const loaderCache: Record<string, LoaderSource[]> = new Proxy(Object.crea
152141
}
153142
return true;
154143
}
155-
});
144+
});

packages/plugin/tests/no-hashtag-description.spec.ts

Lines changed: 80 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,123 @@
1-
import { GraphQLRuleTester } from '../src/testkit';
1+
import { GraphQLRuleTester } from '../src';
22
import rule from '../src/rules/no-hashtag-description';
3-
import { Kind } from 'graphql';
43

54
const ruleTester = new GraphQLRuleTester();
65

76
ruleTester.runGraphQLTests('no-hashtag-description', rule, {
87
valid: [
9-
{
10-
code: /* GraphQL */ `
11-
" test "
12-
type Query {
13-
foo: String
14-
}
15-
`,
16-
},
17-
{
18-
code: /* GraphQL */ `
19-
# Test
8+
/* GraphQL */ `
9+
" Good "
10+
type Query {
11+
foo: String
12+
}
13+
`,
14+
/* GraphQL */ `
15+
# Good
2016
21-
type Query {
22-
foo: String
23-
}
24-
`,
25-
},
26-
{
27-
code: `#import t
17+
type Query {
18+
foo: String
19+
}
20+
# Good
21+
`,
22+
/* GraphQL */ `
23+
#import t
2824
29-
type Query {
30-
foo: String
31-
}
32-
`,
33-
},
34-
{
35-
code: /* GraphQL */ `
36-
# multiline
37-
# multiline
38-
# multiline
39-
# multiline
25+
type Query {
26+
foo: String
27+
}
28+
`,
29+
/* GraphQL */ `
30+
# multiline
31+
# multiline
32+
# multiline
4033
41-
type Query {
42-
foo: String
43-
}
44-
`,
45-
},
46-
{
47-
code: /* GraphQL */ `
48-
type Query {
49-
foo: String
50-
}
34+
type Query {
35+
foo: String
36+
}
37+
`,
38+
/* GraphQL */ `
39+
type Query { # Good
40+
foo: String # Good
41+
} # Good
42+
`,
43+
/* GraphQL */ `
44+
# eslint-disable-next-line
45+
type Query {
46+
foo: String
47+
}
48+
`,
49+
/* GraphQL */ `
50+
type Query {
51+
# Good
5152
52-
# Test
53-
`,
54-
},
53+
foo: ID
54+
}
55+
`,
56+
/* GraphQL */ `
57+
type Query {
58+
foo: ID
59+
# Good
60+
61+
bar: ID
62+
}
63+
`,
64+
/* GraphQL */ `
65+
type Query {
66+
user(
67+
# Good
68+
69+
id: Int
70+
): User
71+
}
72+
`,
73+
],
74+
invalid: [
5575
{
5676
code: /* GraphQL */ `
77+
# Bad
5778
type Query {
58-
foo: String # this is also fine, comes after the definition
59-
}
60-
`,
61-
},
62-
{
63-
code: /* GraphQL */ `
64-
type Query { # this is also fine, comes after the definition
6579
foo: String
66-
} # this is also fine, comes after the definition
80+
}
6781
`,
82+
errors: [{ messageId: 'HASHTAG_COMMENT' }],
6883
},
6984
{
7085
code: /* GraphQL */ `
86+
# multiline
87+
# multiline
7188
type Query {
7289
foo: String
7390
}
74-
75-
# Test
7691
`,
92+
errors: [{ messageId: 'HASHTAG_COMMENT' }],
7793
},
7894
{
7995
code: /* GraphQL */ `
80-
# eslint
8196
type Query {
97+
# Bad
8298
foo: String
8399
}
84100
`,
101+
errors: [{ messageId: 'HASHTAG_COMMENT' }],
85102
},
86-
],
87-
invalid: [
88103
{
89104
code: /* GraphQL */ `
90-
# Test
91105
type Query {
92-
foo: String
106+
bar: ID
107+
# Bad
108+
foo: ID
109+
# Good
93110
}
94111
`,
95112
errors: [{ messageId: 'HASHTAG_COMMENT' }],
96113
},
97114
{
98115
code: /* GraphQL */ `
99116
type Query {
100-
# Test
101-
foo: String
117+
user(
118+
# Bad
119+
id: Int!
120+
): User
102121
}
103122
`,
104123
errors: [{ messageId: 'HASHTAG_COMMENT' }],

0 commit comments

Comments
 (0)