-
Notifications
You must be signed in to change notification settings - Fork 665
Expand file tree
/
Copy pathno-lit-render-outside-of-view.ts
More file actions
105 lines (100 loc) · 4.05 KB
/
no-lit-render-outside-of-view.ts
File metadata and controls
105 lines (100 loc) · 4.05 KB
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
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @file Rule to identify Lit render calls that are not inside of a
* view function.
*/
import type {TSESTree} from '@typescript-eslint/utils';
import {isCommaToken} from '@typescript-eslint/utils/ast-utils';
import {nullThrows, NullThrowsReasons} from '@typescript-eslint/utils/eslint-utils';
import type {ArrowFunctionExpression, FunctionDeclaration, FunctionExpression, Identifier} from 'estree';
import {isLitHtmlRenderCall, isViewFunction} from './utils/lit.ts';
import {createRule} from './utils/ruleCreator.ts';
export default createRule({
name: 'no-lit-render-outside-of-view',
meta: {
type: 'problem',
docs: {
description: 'Lit render calls should be inside of a view function',
category: 'Possible Errors',
},
messages: {
litRenderShouldBeInsideOfView: 'Lit render calls should be inside of a view function',
litRenderInsideOfViewMustUseTarget: 'Lit render calls inside of a view function must use the `target` parameter',
litRenderInsideOfViewMustNotUseHost: 'Lit render calls inside of a view function must not use the `host` option',
},
fixable: 'code',
schema: [], // no options
},
defaultOptions: [],
create: function(context) {
const {sourceCode} = context;
return {
CallExpression(node) {
if (!isLitHtmlRenderCall(node)) {
return;
}
let functionNode: TSESTree.Node|undefined = node.parent;
while (functionNode &&
!['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'].includes(functionNode.type)) {
functionNode = functionNode.parent;
}
if (!functionNode || !isViewFunction(functionNode)) {
context.report({
node,
messageId: 'litRenderShouldBeInsideOfView',
});
return;
}
type FunctionLike = FunctionDeclaration|FunctionExpression|ArrowFunctionExpression;
const targetParameterName = ((functionNode as FunctionLike).params[2] as Identifier).name;
const targetArgument = node.arguments[1];
if (targetArgument.type !== 'Identifier' || targetArgument.name !== targetParameterName) {
context.report({
node,
messageId: 'litRenderInsideOfViewMustUseTarget',
fix(fixer) {
return fixer.replaceText(targetArgument, targetParameterName);
}
});
return;
}
if (node.arguments.length < 3) {
return;
}
const renderOptions = node.arguments[2];
if (renderOptions.type !== 'ObjectExpression') {
// Invalid, but TypeScript will catch it.
return;
}
for (const renderOption of renderOptions.properties) {
if (renderOption.type === 'Property' && renderOption.key.type === 'Identifier' &&
renderOption.key.name === 'host') {
context.report({
node,
messageId: 'litRenderInsideOfViewMustNotUseHost',
fix(fixer) {
if (renderOptions.properties.length === 1) {
const commaToken = nullThrows(
sourceCode.getTokenBefore(renderOptions, isCommaToken),
NullThrowsReasons.MissingToken(',', node.type));
return fixer.removeRange([commaToken.range[0], renderOptions.range[1]]);
}
const prevToken = sourceCode.getTokenBefore(renderOption);
if (prevToken && isCommaToken(prevToken)) {
return fixer.removeRange([prevToken.range[0], renderOption.range[1]]);
}
const nextToken = sourceCode.getTokenAfter(renderOption);
if (nextToken && isCommaToken(nextToken)) {
return fixer.removeRange([renderOption.range[0], nextToken.range[1]]);
}
return [];
}
});
}
}
},
};
}
});