@lark-apaas/fullstack-presets 1.1.18 → 1.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/custom-eslint-rules/index.d.ts +2 -0
- package/lib/custom-eslint-rules/index.js +4 -0
- package/lib/custom-eslint-rules/no-multiline-styled-jsx-classname.d.ts +36 -0
- package/lib/custom-eslint-rules/no-multiline-styled-jsx-classname.js +157 -0
- package/lib/custom-eslint-rules/no-styled-jsx-data-uri-url-ref.d.ts +39 -0
- package/lib/custom-eslint-rules/no-styled-jsx-data-uri-url-ref.js +142 -0
- package/lib/recommend/eslint/eslint-client.d.ts +2 -0
- package/lib/recommend/eslint/eslint-client.js +4 -0
- package/lib/recommend/eslint/index.d.ts +2 -0
- package/lib/recommend/eslint/index.js +1 -1
- package/lib/simple/recommend/eslint/eslint-client.js +4 -0
- package/lib/simple/recommend/eslint/index.d.ts +4 -0
- package/lib/simple/recommend/eslint/index.js +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export declare const customRules: {
|
|
2
2
|
'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
|
|
3
|
+
'no-styled-jsx-data-uri-url-ref': import("eslint").Rule.RuleModule;
|
|
4
|
+
'no-multiline-styled-jsx-classname': import("eslint").Rule.RuleModule;
|
|
3
5
|
'require-app-container': import("eslint").Rule.RuleModule;
|
|
4
6
|
'no-direct-capability-api': import("eslint").Rule.RuleModule;
|
|
5
7
|
'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
|
|
@@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.customRules = void 0;
|
|
7
7
|
const no_nested_styled_jsx_1 = __importDefault(require("./no-nested-styled-jsx"));
|
|
8
|
+
const no_styled_jsx_data_uri_url_ref_1 = __importDefault(require("./no-styled-jsx-data-uri-url-ref"));
|
|
9
|
+
const no_multiline_styled_jsx_classname_1 = __importDefault(require("./no-multiline-styled-jsx-classname"));
|
|
8
10
|
const require_app_container_1 = __importDefault(require("./require-app-container"));
|
|
9
11
|
const no_direct_capability_api_1 = __importDefault(require("./no-direct-capability-api"));
|
|
10
12
|
const require_scroll_reveal_hook_1 = __importDefault(require("./require-scroll-reveal-hook"));
|
|
@@ -16,6 +18,8 @@ const require_index_route_1 = __importDefault(require("./require-index-route"));
|
|
|
16
18
|
const no_duplicate_route_component_1 = __importDefault(require("./no-duplicate-route-component"));
|
|
17
19
|
exports.customRules = {
|
|
18
20
|
'no-nested-styled-jsx': no_nested_styled_jsx_1.default,
|
|
21
|
+
'no-styled-jsx-data-uri-url-ref': no_styled_jsx_data_uri_url_ref_1.default,
|
|
22
|
+
'no-multiline-styled-jsx-classname': no_multiline_styled_jsx_classname_1.default,
|
|
19
23
|
'require-app-container': require_app_container_1.default,
|
|
20
24
|
'no-direct-capability-api': no_direct_capability_api_1.default,
|
|
21
25
|
'require-scroll-reveal-hook': require_scroll_reveal_hook_1.default,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule: no-multiline-styled-jsx-classname
|
|
3
|
+
*
|
|
4
|
+
* Detects multi-line className attributes (TemplateLiteral or JSX string attr
|
|
5
|
+
* with literal newlines) on JSX elements that belong to a function component
|
|
6
|
+
* which ALSO contains a `<style jsx>` tag.
|
|
7
|
+
*
|
|
8
|
+
* Why scope to the enclosing function component (not the whole file)?
|
|
9
|
+
* styled-jsx's Babel plugin only wraps className when the JSXElement tree
|
|
10
|
+
* actually contains a `<style jsx>` tag. A file can have multiple function
|
|
11
|
+
* components; flagging multi-line classNames in components that never use
|
|
12
|
+
* styled-jsx would be a false positive (their className never gets wrapped,
|
|
13
|
+
* so no downstream transpiler bug can bite them).
|
|
14
|
+
*
|
|
15
|
+
* When the rule DOES fire, styled-jsx wraps the className as:
|
|
16
|
+
*
|
|
17
|
+
* className={"jsx-X" + " " + `\n bg-primary\n`}
|
|
18
|
+
*
|
|
19
|
+
* Some downstream transpilers (esbuild/swc/terser at certain target settings)
|
|
20
|
+
* lower simple TemplateLiterals (no ${}) to StringLiterals but fail to escape
|
|
21
|
+
* the embedded newlines, producing invalid JS:
|
|
22
|
+
*
|
|
23
|
+
* className={"jsx-X" + " " + "\n bg-primary\n"}
|
|
24
|
+
* // → "Unterminated string literal" build error
|
|
25
|
+
*
|
|
26
|
+
* The fix is simple: write className as a single-line string. CSS class lists
|
|
27
|
+
* treat any whitespace run as one separator, so collapsing is semantically
|
|
28
|
+
* equivalent.
|
|
29
|
+
*
|
|
30
|
+
* Observed: ~59 of 83 Unterminated cases in week 04-06~04-12.
|
|
31
|
+
*
|
|
32
|
+
* @see https://bytedance.larkoffice.com/wiki/TTNDwzgLzi9Q0skTUT5clFX3nNd
|
|
33
|
+
*/
|
|
34
|
+
import type { Rule } from 'eslint';
|
|
35
|
+
declare const rule: Rule.RuleModule;
|
|
36
|
+
export default rule;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/** Is this JSXOpeningElement a `<style jsx>` tag? */
|
|
4
|
+
function isStyledJsxOpening(node) {
|
|
5
|
+
if (!node)
|
|
6
|
+
return false;
|
|
7
|
+
if (node.type !== 'JSXOpeningElement')
|
|
8
|
+
return false;
|
|
9
|
+
const name = node.name;
|
|
10
|
+
if (name?.type !== 'JSXIdentifier' || name.name !== 'style')
|
|
11
|
+
return false;
|
|
12
|
+
return (node.attributes?.some((attr) => attr.type === 'JSXAttribute' &&
|
|
13
|
+
attr.name?.type === 'JSXIdentifier' &&
|
|
14
|
+
attr.name.name === 'jsx') ?? false);
|
|
15
|
+
}
|
|
16
|
+
const rule = {
|
|
17
|
+
meta: {
|
|
18
|
+
type: 'problem',
|
|
19
|
+
docs: {
|
|
20
|
+
description: 'Disallow multi-line className in components using <style jsx>',
|
|
21
|
+
category: 'Possible Errors',
|
|
22
|
+
recommended: true,
|
|
23
|
+
},
|
|
24
|
+
fixable: 'code',
|
|
25
|
+
schema: [],
|
|
26
|
+
messages: {
|
|
27
|
+
multilineClassName: '多行 className 在 styled-jsx 编译后可能被下游构建工具截断为非法字符串' +
|
|
28
|
+
'("Unterminated string literal")。请改为单行写法。',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
create(context) {
|
|
32
|
+
// A "scope" is the OUTERMOST enclosing function component in the current
|
|
33
|
+
// traversal path (i.e., the first function after Program in the stack),
|
|
34
|
+
// or Program itself if no function is on the stack.
|
|
35
|
+
//
|
|
36
|
+
// Why "outermost" and not "nearest"?
|
|
37
|
+
// styled-jsx's Babel plugin wraps every className in the ENTIRE JSX tree
|
|
38
|
+
// of a component that contains `<style jsx>`. So a className inside
|
|
39
|
+
// `list.map((item) => <button className="..." />)` within PropsPage
|
|
40
|
+
// still gets wrapped — the arrow function is just a render helper, not
|
|
41
|
+
// a separate component. Scoping by nearest enclosing function would
|
|
42
|
+
// incorrectly exempt such inline callbacks.
|
|
43
|
+
//
|
|
44
|
+
// Implementation:
|
|
45
|
+
// - Stack entries are pushed for Program and every function node
|
|
46
|
+
// encountered (so nesting is preserved structurally).
|
|
47
|
+
// - `currentScope()` returns stack[1] — the first function below Program
|
|
48
|
+
// — if one exists, else stack[0] (Program). This effectively treats all
|
|
49
|
+
// nested functions as members of the outer function's scope.
|
|
50
|
+
// - Separate top-level function components (declared at Program level)
|
|
51
|
+
// get their own scope because only one of them is on the stack at a
|
|
52
|
+
// time (ComponentA pushes + pops before ComponentB pushes).
|
|
53
|
+
const scopeStack = [];
|
|
54
|
+
// WeakSet keeps references without preventing GC; matches by object identity.
|
|
55
|
+
const scopesWithStyledJsx = new WeakSet();
|
|
56
|
+
const candidates = [];
|
|
57
|
+
const currentScope = () => {
|
|
58
|
+
if (scopeStack.length === 0)
|
|
59
|
+
return null;
|
|
60
|
+
// stack[0] is Program; stack[1], if present, is the outermost function
|
|
61
|
+
// component. Everything deeper is considered part of stack[1]'s scope.
|
|
62
|
+
return scopeStack.length >= 2 ? scopeStack[1] : scopeStack[0];
|
|
63
|
+
};
|
|
64
|
+
const pushScope = (node) => scopeStack.push(node);
|
|
65
|
+
const popScope = () => {
|
|
66
|
+
scopeStack.pop();
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
// --- Scope tracking (function components) ---
|
|
70
|
+
Program(node) {
|
|
71
|
+
pushScope(node);
|
|
72
|
+
},
|
|
73
|
+
'Program:exit'() {
|
|
74
|
+
// Finalize: pop Program scope + run reports
|
|
75
|
+
popScope();
|
|
76
|
+
for (const { target, raw, kind, scope } of candidates) {
|
|
77
|
+
if (!scopesWithStyledJsx.has(scope))
|
|
78
|
+
continue;
|
|
79
|
+
const text = kind === 'template'
|
|
80
|
+
? (target.quasis[0]?.value?.cooked ?? raw)
|
|
81
|
+
: raw;
|
|
82
|
+
const normalized = text.replace(/\s+/g, ' ').trim();
|
|
83
|
+
context.report({
|
|
84
|
+
node: target,
|
|
85
|
+
messageId: 'multilineClassName',
|
|
86
|
+
fix(fixer) {
|
|
87
|
+
// Both Case A (template) and Case B (string) collapse to a
|
|
88
|
+
// single-line StringLiteral. Safe because className list
|
|
89
|
+
// semantics treat any whitespace run as one separator.
|
|
90
|
+
return fixer.replaceText(target, JSON.stringify(normalized));
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
FunctionDeclaration(node) {
|
|
96
|
+
pushScope(node);
|
|
97
|
+
},
|
|
98
|
+
'FunctionDeclaration:exit'() {
|
|
99
|
+
popScope();
|
|
100
|
+
},
|
|
101
|
+
FunctionExpression(node) {
|
|
102
|
+
pushScope(node);
|
|
103
|
+
},
|
|
104
|
+
'FunctionExpression:exit'() {
|
|
105
|
+
popScope();
|
|
106
|
+
},
|
|
107
|
+
ArrowFunctionExpression(node) {
|
|
108
|
+
pushScope(node);
|
|
109
|
+
},
|
|
110
|
+
'ArrowFunctionExpression:exit'() {
|
|
111
|
+
popScope();
|
|
112
|
+
},
|
|
113
|
+
// --- Mark scope as containing <style jsx> ---
|
|
114
|
+
JSXOpeningElement(node) {
|
|
115
|
+
if (!isStyledJsxOpening(node))
|
|
116
|
+
return;
|
|
117
|
+
const scope = currentScope();
|
|
118
|
+
if (scope)
|
|
119
|
+
scopesWithStyledJsx.add(scope);
|
|
120
|
+
},
|
|
121
|
+
// --- Collect multi-line className candidates ---
|
|
122
|
+
JSXAttribute(node) {
|
|
123
|
+
if (node.name?.type !== 'JSXIdentifier' ||
|
|
124
|
+
node.name.name !== 'className') {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const val = node.value;
|
|
128
|
+
if (!val)
|
|
129
|
+
return;
|
|
130
|
+
const scope = currentScope();
|
|
131
|
+
if (!scope)
|
|
132
|
+
return; // no enclosing scope (shouldn't happen since Program is pushed)
|
|
133
|
+
// Case A: className={`...\n...`} — TemplateLiteral without ${}
|
|
134
|
+
if (val.type === 'JSXExpressionContainer') {
|
|
135
|
+
const expr = val.expression;
|
|
136
|
+
if (!expr || expr.type !== 'TemplateLiteral')
|
|
137
|
+
return;
|
|
138
|
+
if (expr.expressions.length !== 0)
|
|
139
|
+
return;
|
|
140
|
+
const raw = expr.quasis[0]?.value?.raw ?? '';
|
|
141
|
+
if (!raw.includes('\n') && !raw.includes('\r'))
|
|
142
|
+
return;
|
|
143
|
+
candidates.push({ target: expr, raw, kind: 'template', scope });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// Case B: className="...\n..." — JSX string attribute with newlines
|
|
147
|
+
if (val.type === 'Literal' || val.type === 'StringLiteral') {
|
|
148
|
+
const str = val.value ?? '';
|
|
149
|
+
if (!str.includes('\n') && !str.includes('\r'))
|
|
150
|
+
return;
|
|
151
|
+
candidates.push({ target: val, raw: str, kind: 'string', scope });
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
exports.default = rule;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule: no-styled-jsx-data-uri-url-ref
|
|
3
|
+
*
|
|
4
|
+
* Detects CSS `url("data:...url(...)...")` inside `<style jsx>` template
|
|
5
|
+
* literals. The stylis CSS parser (used by styled-jsx) misreads the inner
|
|
6
|
+
* `url(` as a new CSS url() function call, breaking bracket counting and
|
|
7
|
+
* causing "Nesting detected" build failures.
|
|
8
|
+
*
|
|
9
|
+
* This rule surfaces the problem at lint time (IDE red squiggly + CI gate)
|
|
10
|
+
* instead of letting it escape to build time.
|
|
11
|
+
*
|
|
12
|
+
* Observed: 107 cases in week 04-06~04-12, all from AI-generated SVG noise
|
|
13
|
+
* textures containing `filter='url(%23noise)'` inside data URIs.
|
|
14
|
+
*
|
|
15
|
+
* @see ~/docs/styled-jsx-nesting-bug-analysis-20260330.md
|
|
16
|
+
* @see https://bytedance.larkoffice.com/wiki/TTNDwzgLzi9Q0skTUT5clFX3nNd
|
|
17
|
+
*/
|
|
18
|
+
import type { Rule } from 'eslint';
|
|
19
|
+
/**
|
|
20
|
+
* Precise detection: scan the CSS text character-by-character, looking for
|
|
21
|
+
* `url(<quote>data:...<quote>)` atoms. For each such atom, check whether the
|
|
22
|
+
* body (between the matching quotes) contains `url(`. Only flag if the
|
|
23
|
+
* offending `url(` is truly nested inside a data URI body — this eliminates
|
|
24
|
+
* the false-positive case where a data URI and a separate `url(#gradient)`
|
|
25
|
+
* coexist in the same CSS but are unrelated.
|
|
26
|
+
*
|
|
27
|
+
* Safety properties (can't hang / crash lint):
|
|
28
|
+
* - Single forward scan, O(n); `i` always advances past every candidate.
|
|
29
|
+
* - Bounded loop: hard limit on total iterations to protect against pathological
|
|
30
|
+
* input.
|
|
31
|
+
* - Handles escaped quotes (\") inside data URI bodies.
|
|
32
|
+
* - Bails safely (returns false) on unterminated strings.
|
|
33
|
+
* - Zero regex backtracking; no catastrophic-regex risk.
|
|
34
|
+
*
|
|
35
|
+
* @param cssText the full CSS source of a single <style jsx> block
|
|
36
|
+
*/
|
|
37
|
+
export declare function hasDataUriWithNestedUrl(cssText: string): boolean;
|
|
38
|
+
declare const rule: Rule.RuleModule;
|
|
39
|
+
export default rule;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hasDataUriWithNestedUrl = hasDataUriWithNestedUrl;
|
|
4
|
+
/**
|
|
5
|
+
* Precise detection: scan the CSS text character-by-character, looking for
|
|
6
|
+
* `url(<quote>data:...<quote>)` atoms. For each such atom, check whether the
|
|
7
|
+
* body (between the matching quotes) contains `url(`. Only flag if the
|
|
8
|
+
* offending `url(` is truly nested inside a data URI body — this eliminates
|
|
9
|
+
* the false-positive case where a data URI and a separate `url(#gradient)`
|
|
10
|
+
* coexist in the same CSS but are unrelated.
|
|
11
|
+
*
|
|
12
|
+
* Safety properties (can't hang / crash lint):
|
|
13
|
+
* - Single forward scan, O(n); `i` always advances past every candidate.
|
|
14
|
+
* - Bounded loop: hard limit on total iterations to protect against pathological
|
|
15
|
+
* input.
|
|
16
|
+
* - Handles escaped quotes (\") inside data URI bodies.
|
|
17
|
+
* - Bails safely (returns false) on unterminated strings.
|
|
18
|
+
* - Zero regex backtracking; no catastrophic-regex risk.
|
|
19
|
+
*
|
|
20
|
+
* @param cssText the full CSS source of a single <style jsx> block
|
|
21
|
+
*/
|
|
22
|
+
function hasDataUriWithNestedUrl(cssText) {
|
|
23
|
+
const len = cssText.length;
|
|
24
|
+
const MAX_ITERATIONS = len + 1; // guard against logic bugs
|
|
25
|
+
let iterations = 0;
|
|
26
|
+
let i = 0;
|
|
27
|
+
while (i < len) {
|
|
28
|
+
if (++iterations > MAX_ITERATIONS)
|
|
29
|
+
return false; // safety fuse
|
|
30
|
+
// Find the next literal "url(" atom
|
|
31
|
+
const urlIdx = cssText.indexOf('url(', i);
|
|
32
|
+
if (urlIdx === -1)
|
|
33
|
+
return false;
|
|
34
|
+
i = urlIdx + 4;
|
|
35
|
+
// Skip whitespace after "url("
|
|
36
|
+
while (i < len && (cssText[i] === ' ' || cssText[i] === '\t' || cssText[i] === '\n')) {
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
if (i >= len)
|
|
40
|
+
return false;
|
|
41
|
+
// Must be quoted
|
|
42
|
+
const quote = cssText[i];
|
|
43
|
+
if (quote !== '"' && quote !== "'")
|
|
44
|
+
continue;
|
|
45
|
+
// Must be "data:" scheme
|
|
46
|
+
if (cssText.slice(i + 1, i + 6) !== 'data:')
|
|
47
|
+
continue;
|
|
48
|
+
// Walk char-by-char to find matching close quote, honouring \" escapes
|
|
49
|
+
const bodyStart = i + 1;
|
|
50
|
+
let j = bodyStart;
|
|
51
|
+
let terminated = false;
|
|
52
|
+
while (j < len) {
|
|
53
|
+
const c = cssText[j];
|
|
54
|
+
if (c === '\\' && j + 1 < len) {
|
|
55
|
+
j += 2;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (c === quote) {
|
|
59
|
+
terminated = true;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
j++;
|
|
63
|
+
}
|
|
64
|
+
if (!terminated)
|
|
65
|
+
return false; // unterminated data URI — bail safely
|
|
66
|
+
const body = cssText.slice(bodyStart, j);
|
|
67
|
+
// If the data URI body contains a literal `url(`, stylis will break.
|
|
68
|
+
if (body.indexOf('url(') !== -1)
|
|
69
|
+
return true;
|
|
70
|
+
// Move past this data URI and keep scanning for more url() atoms.
|
|
71
|
+
i = j + 1;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const rule = {
|
|
76
|
+
meta: {
|
|
77
|
+
type: 'problem',
|
|
78
|
+
docs: {
|
|
79
|
+
description: 'Disallow data URIs whose body contains url() references inside <style jsx>, which crash the stylis CSS parser',
|
|
80
|
+
category: 'Possible Errors',
|
|
81
|
+
recommended: true,
|
|
82
|
+
},
|
|
83
|
+
schema: [],
|
|
84
|
+
messages: {
|
|
85
|
+
dataUriUrlRef: '`url("data:...")` 的内容里嵌套了 `url(...)` 引用(如 SVG filter 的 `url(#id)`),会导致 styled-jsx 的 CSS 解析器(stylis)' +
|
|
86
|
+
'误判括号配对并报 "Nesting detected" 构建错误。请将 SVG 移到独立 .svg 文件通过 import 引用,' +
|
|
87
|
+
'或改用 CSS 变量 / 外链 background-image 代替内联 data URI。',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
create(context) {
|
|
91
|
+
return {
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
93
|
+
JSXElement(node) {
|
|
94
|
+
// Only look at <style jsx> or <style jsx global>
|
|
95
|
+
const opening = node.openingElement;
|
|
96
|
+
if (!opening)
|
|
97
|
+
return;
|
|
98
|
+
const name = opening.name;
|
|
99
|
+
if (name?.type !== 'JSXIdentifier' || name.name !== 'style')
|
|
100
|
+
return;
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
|
+
const hasJsx = opening.attributes.some(
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
104
|
+
(attr) => attr.type === 'JSXAttribute' &&
|
|
105
|
+
attr.name?.type === 'JSXIdentifier' &&
|
|
106
|
+
attr.name.name === 'jsx');
|
|
107
|
+
if (!hasJsx)
|
|
108
|
+
return;
|
|
109
|
+
// Walk children looking for JSXExpressionContainer → TemplateLiteral / StringLiteral
|
|
110
|
+
for (const child of node.children || []) {
|
|
111
|
+
if (child.type !== 'JSXExpressionContainer')
|
|
112
|
+
continue;
|
|
113
|
+
const expr = child.expression;
|
|
114
|
+
if (!expr)
|
|
115
|
+
continue;
|
|
116
|
+
let cssText = null;
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
118
|
+
let targetNode = null;
|
|
119
|
+
if (expr.type === 'TemplateLiteral' && expr.quasis?.length > 0) {
|
|
120
|
+
cssText = expr.quasis
|
|
121
|
+
.map((q) => q.value.raw)
|
|
122
|
+
.join('');
|
|
123
|
+
targetNode = expr;
|
|
124
|
+
}
|
|
125
|
+
else if (expr.type === 'Literal' && typeof expr.value === 'string') {
|
|
126
|
+
cssText = expr.value;
|
|
127
|
+
targetNode = expr;
|
|
128
|
+
}
|
|
129
|
+
if (!cssText || !targetNode)
|
|
130
|
+
continue;
|
|
131
|
+
if (hasDataUriWithNestedUrl(cssText)) {
|
|
132
|
+
context.report({
|
|
133
|
+
node: targetNode,
|
|
134
|
+
messageId: 'dataUriUrlRef',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
exports.default = rule;
|
|
@@ -16,6 +16,8 @@ declare const _default: {
|
|
|
16
16
|
'@lark-apaas': {
|
|
17
17
|
rules: {
|
|
18
18
|
'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
|
|
19
|
+
'no-styled-jsx-data-uri-url-ref': import("eslint").Rule.RuleModule;
|
|
20
|
+
'no-multiline-styled-jsx-classname': import("eslint").Rule.RuleModule;
|
|
19
21
|
'require-app-container': import("eslint").Rule.RuleModule;
|
|
20
22
|
'no-direct-capability-api': import("eslint").Rule.RuleModule;
|
|
21
23
|
'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
|
|
@@ -42,6 +42,10 @@ exports.default = {
|
|
|
42
42
|
'@lark-apaas/require-app-container': 'error',
|
|
43
43
|
// 平台规则:禁止直接调用 capability 内部 API,应使用 capabilityClient
|
|
44
44
|
'@lark-apaas/no-direct-capability-api': 'error',
|
|
45
|
+
// styled-jsx data URI + url(#) 会导致 stylis 误判括号配对 → "Nesting detected" 构建失败
|
|
46
|
+
'@lark-apaas/no-styled-jsx-data-uri-url-ref': 'error',
|
|
47
|
+
// styled-jsx + 多行模板 className → 下游 transpiler 可能产出 "Unterminated string literal"
|
|
48
|
+
'@lark-apaas/no-multiline-styled-jsx-classname': 'error',
|
|
45
49
|
// TypeScript 规则
|
|
46
50
|
'@typescript-eslint/no-unused-vars': 'off', // 未使用变量检查关闭
|
|
47
51
|
'@typescript-eslint/no-explicit-any': 'off', // 允许使用 any 类型
|
|
@@ -19,6 +19,8 @@ export declare const eslintPresets: {
|
|
|
19
19
|
'@lark-apaas': {
|
|
20
20
|
rules: {
|
|
21
21
|
'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
|
|
22
|
+
'no-styled-jsx-data-uri-url-ref': import("eslint").Rule.RuleModule;
|
|
23
|
+
'no-multiline-styled-jsx-classname': import("eslint").Rule.RuleModule;
|
|
22
24
|
'require-app-container': import("eslint").Rule.RuleModule;
|
|
23
25
|
'no-direct-capability-api': import("eslint").Rule.RuleModule;
|
|
24
26
|
'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
|
|
@@ -20,7 +20,7 @@ const testFileIgnorePatterns = [
|
|
|
20
20
|
'**/*.spec.ts',
|
|
21
21
|
'**/*.spec.tsx',
|
|
22
22
|
];
|
|
23
|
-
const globalIgnoreServerPatterns = ['server/database/.introspect/**', ...testFileIgnorePatterns]; // 全局 eslint server ignore
|
|
23
|
+
const globalIgnoreServerPatterns = ['tmp/**', 'server/database/.introspect/**', ...testFileIgnorePatterns]; // 全局 eslint server ignore
|
|
24
24
|
const globalIgnoreClientPatterns = ['dist', 'node_modules', 'client/src/api/gen', ...testFileIgnorePatterns]; // 全局 eslint client Ignore
|
|
25
25
|
// 只导出纯净的规则配置,不包含基础配置
|
|
26
26
|
// 用户需要在项目配置中自己添加基础配置(eslintJs, tseslint, nestjs 等)
|
|
@@ -169,6 +169,10 @@ const baseConfig = {
|
|
|
169
169
|
// 平台规则:禁止直接调用 capability 内部 API,应使用 capabilityClient
|
|
170
170
|
'@lark-apaas/no-direct-capability-api': 'error',
|
|
171
171
|
'@lark-apaas/no-nested-styled-jsx': 'error',
|
|
172
|
+
// styled-jsx data URI + url(#) 会导致 stylis 误判括号配对 → "Nesting detected" 构建失败
|
|
173
|
+
'@lark-apaas/no-styled-jsx-data-uri-url-ref': 'error',
|
|
174
|
+
// styled-jsx + 多行模板 className → 下游 transpiler 可能产出 "Unterminated string literal"
|
|
175
|
+
'@lark-apaas/no-multiline-styled-jsx-classname': 'error',
|
|
172
176
|
// 自定义规则:HTTP 调用响应类型必须定义在 shared/(默认关闭,需 y 位升级后对新应用开启)
|
|
173
177
|
'@lark-apaas/require-shared-request-type': 'off',
|
|
174
178
|
// TypeScript 规则
|
|
@@ -46,6 +46,8 @@ export declare const eslintPresets: {
|
|
|
46
46
|
'@lark-apaas': {
|
|
47
47
|
rules: {
|
|
48
48
|
'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
|
|
49
|
+
'no-styled-jsx-data-uri-url-ref': import("eslint").Rule.RuleModule;
|
|
50
|
+
'no-multiline-styled-jsx-classname': import("eslint").Rule.RuleModule;
|
|
49
51
|
'require-app-container': import("eslint").Rule.RuleModule;
|
|
50
52
|
'no-direct-capability-api': import("eslint").Rule.RuleModule;
|
|
51
53
|
'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
|
|
@@ -69,6 +71,8 @@ export declare const eslintPresets: {
|
|
|
69
71
|
'@lark-apaas': {
|
|
70
72
|
rules: {
|
|
71
73
|
'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
|
|
74
|
+
'no-styled-jsx-data-uri-url-ref': import("eslint").Rule.RuleModule;
|
|
75
|
+
'no-multiline-styled-jsx-classname': import("eslint").Rule.RuleModule;
|
|
72
76
|
'require-app-container': import("eslint").Rule.RuleModule;
|
|
73
77
|
'no-direct-capability-api': import("eslint").Rule.RuleModule;
|
|
74
78
|
'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
|
|
@@ -21,7 +21,7 @@ const testFileIgnorePatterns = [
|
|
|
21
21
|
'**/*.spec.ts',
|
|
22
22
|
'**/*.spec.tsx',
|
|
23
23
|
];
|
|
24
|
-
const globalIgnoreServerPatterns = ['server/database/.introspect/**', ...testFileIgnorePatterns]; // 全局 eslint server ignore
|
|
24
|
+
const globalIgnoreServerPatterns = ['tmp/**', 'server/database/.introspect/**', ...testFileIgnorePatterns]; // 全局 eslint server ignore
|
|
25
25
|
const globalIgnoreClientPatterns = ['dist', 'node_modules', 'client/src/api/gen', ...testFileIgnorePatterns]; // 全局 eslint client Ignore
|
|
26
26
|
// @lark-apaas 插件只注册一次,client 和 server config 共享
|
|
27
27
|
const larkApaasPlugin = {
|