-
Notifications
You must be signed in to change notification settings - Fork 498
/
Copy pathlit-no-attribute-quotes.ts
111 lines (100 loc) · 4.62 KB
/
lit-no-attribute-quotes.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import type {TSESLint, TSESTree} from '@typescript-eslint/utils';
import {isLitHtmlTemplateCall} from './utils/lit.ts';
import {createRule} from './utils/ruleCreator.ts';
// Define types based on TSESTree
type TemplateElement = TSESTree.TemplateElement;
type RuleFixer = TSESLint.RuleFixer;
type RuleFix = TSESLint.RuleFix; // Type for individual fixes
// Define MessageIds used in the rule
type MessageIds = 'attributeQuotesNotRequired';
function templateElementPartStartsWithDoubleQuote(templateElementPartNode: TemplateElement): boolean {
return templateElementPartNode.value.raw.startsWith('"');
}
function templateElementPartEndsWithEqualsDoubleQuote(templateElementPartNode: TemplateElement): boolean {
return templateElementPartNode.value.raw.endsWith('="');
}
interface RemoveQuotesParams {
fixer: RuleFixer;
firstPart: TemplateElement;
secondPart: TemplateElement;
}
// Return type is an array of fixes or potentially null/empty if ranges are invalid
function removeQuotesFromAttribute({fixer, firstPart, secondPart}: RemoveQuotesParams): RuleFix[]|null {
const [, rangeOfOpeningTemplatePartEnd] = firstPart.range;
// From the first part, we need to remove the last character, which is the double quote.
// We can do this by fetching the range of the node (range = start and end position on the line)
// However, for the template part with the opening quote, the range will also contain the ${ of the interpolation:
// <p class="${
// ^^^^^^^^^^^^ this is the text covered by the [start, end] range.
// So what we need to do is remove the quote, and leave the last two characters alone.
// Therefore we remove the third character back from the end, and only remove a single character, leaving the ${ part alone.
const startingQuoteRangeToRemove: [number, number] =
[rangeOfOpeningTemplatePartEnd - 3, rangeOfOpeningTemplatePartEnd - 2];
const [rangeOfClosingTemplatePartStart] = secondPart.range;
// It's a similar story for the second part where the range includes the }:
// }">foo</p>
// ^^^^^^^^^^ this is the range
// So therefore we get the start of the range, and add one to it, to dodge the } character, and then remove only the quote.
const endingQuoteRangeToRemove: [number, number] =
[rangeOfClosingTemplatePartStart + 1, rangeOfClosingTemplatePartStart + 2];
return [fixer.removeRange(startingQuoteRangeToRemove), fixer.removeRange(endingQuoteRangeToRemove)];
}
export default createRule<[], MessageIds>({
name: 'lit-no-attribute-quotes',
meta: {
type: 'problem',
docs: {
description: 'ensure no extra quotes around attributes when the value is interpolated',
category: 'Possible Errors',
},
fixable: 'code',
messages: {
attributeQuotesNotRequired:
'When interpolating a value as an attribute in Lit you do not need double quotes around it.',
},
schema: []
},
defaultOptions: [],
create: function(context) {
return {
TaggedTemplateExpression(node) {
if (!isLitHtmlTemplateCall(node)) {
return;
}
// quasis here = the static parts of a template expression
// expressions = the dynamic parts of a template expression
// For example, given: <p class="${foo}"> we will have:
// quasis: ['<p class="', '">']
// expressions: ['foo'] (it's actually an AST node representing ${foo})
// So what we do is walk through and look for quasi pairs where:
// 1. the first ends with ="
// 2. the second begins with "
// We can then be confident that we have found an attribute with quotes around it.
node.quasi.quasis.forEach((templateElement, index) => {
if (templateElementPartEndsWithEqualsDoubleQuote(templateElement)) {
const nextElement = node.quasi.quasis[index + 1];
if (nextElement && templateElementPartStartsWithDoubleQuote(nextElement)) {
const expressionBetweenTheParts = node.quasi.expressions?.[index];
if (expressionBetweenTheParts) {
context.report({
node: expressionBetweenTheParts,
messageId: 'attributeQuotesNotRequired',
fix(fixer) {
return removeQuotesFromAttribute({
fixer,
firstPart: templateElement,
secondPart: nextElement,
});
}
});
}
}
}
});
},
};
}
});