forked from angular-eslint/angular-eslint
/
no-inline-styles.ts
129 lines (117 loc) · 3.47 KB
/
no-inline-styles.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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import type {
ParseSourceSpan,
TmplAstElement,
} from '@angular-eslint/bundled-angular-compiler';
import { getTemplateParserServices } from '@angular-eslint/utils';
import { createESLintRule } from '../utils/create-eslint-rule';
type Options = [
{
readonly allowNgStyle?: boolean;
readonly allowBindToStyle?: boolean;
},
];
const DEFAULT_OPTIONS: Options[number] = {
allowNgStyle: false,
allowBindToStyle: false,
};
export type MessageIds = 'noInlineStyles';
export const RULE_NAME = 'no-inline-styles';
export default createESLintRule<Options, MessageIds>({
name: RULE_NAME,
meta: {
type: 'suggestion',
docs: {
description: 'Disallows the use of inline styles in HTML templates',
recommended: false,
},
schema: [
{
type: 'object',
properties: {
allowNgStyle: {
type: 'boolean',
default: DEFAULT_OPTIONS.allowNgStyle,
},
allowBindToStyle: {
type: 'boolean',
default: DEFAULT_OPTIONS.allowBindToStyle,
},
},
additionalProperties: false,
},
],
messages: {
noInlineStyles:
'<{{element}}/> element should not have inline styles via style attribute. Please use classes instead.',
},
},
defaultOptions: [DEFAULT_OPTIONS],
create(context, [{ allowNgStyle, allowBindToStyle }]) {
const parserServices = getTemplateParserServices(context);
return {
Element$1(node: TmplAstElement) {
let isInvalid = false;
if (!allowNgStyle && !allowBindToStyle) {
isInvalid =
isNodeHasStyleAttribute(node) ||
isNodeHasNgStyleAttribute(node) ||
isNodeHasBindingToStyleAttribute(node);
} else {
const ngStyle = allowNgStyle
? false
: isNodeHasNgStyleAttribute(node);
const bindToStyle = allowBindToStyle
? false
: isNodeHasBindingToStyleAttribute(node);
isInvalid = isNodeHasStyleAttribute(node) || ngStyle || bindToStyle;
}
if (isInvalid) {
const loc = parserServices.convertElementSourceSpanToLoc(
context,
node,
);
context.report({
loc,
messageId: 'noInlineStyles',
data: {
element: node.name,
},
});
}
},
};
},
});
/**
* Check that the any element for example `<img>` has a `style` attribute or `attr.style` binding.
*/
function isNodeHasStyleAttribute(node: TmplAstElement): boolean {
return (
node.attributes.some(({ name }) => isStyle(name)) ||
node.inputs.some(({ name }) => isStyle(name))
);
}
/**
* Check that the any element for example `<img>` has a `ngStyle` attribute binding.
*/
function isNodeHasNgStyleAttribute(node: TmplAstElement): boolean {
return node.inputs.some(({ name }) => isNgStyle(name));
}
/**
* Check that the any element for example `<img>` has a `[style.background-color]` attribute binding.
*/
function isNodeHasBindingToStyleAttribute(node: TmplAstElement): boolean {
return node.inputs.some(({ keySpan }) => isStyleBound(keySpan));
}
/**
* Check element is style
*/
function isStyle(name: string): name is 'style' {
return name === 'style';
}
function isNgStyle(name: string): name is 'ngStyle' {
return name === 'ngStyle';
}
function isStyleBound(keySpan: ParseSourceSpan): boolean {
return keySpan?.details ? keySpan.details.includes('style.') : false;
}