@rdjperron/lint 0.1.0 → 0.3.0
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/README.md +147 -57
- package/dist/config.d.mts +122 -0
- package/dist/config.mjs +218 -0
- package/dist/config.mjs.map +1 -0
- package/dist/index.d.mts +5 -0
- package/dist/{anti-slop.mjs → index.mjs} +290 -97
- package/dist/index.mjs.map +1 -0
- package/dist/verify.d.mts +92 -0
- package/dist/verify.mjs +400 -0
- package/dist/verify.mjs.map +1 -0
- package/fixtures/README.md +22 -23
- package/fixtures/{anti-slop/no-array-filter-map.fixture.ts → no-array-filter-map.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-chained-type-assertions.fixture.ts → no-chained-type-assertions.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-conditional-empty-object-spread.fixture.ts → no-conditional-empty-object-spread.fixture.ts} +1 -1
- package/fixtures/{i18n/no-hardcoded-jsx-text.fixture.tsx → no-hardcoded-jsx-text.fixture.tsx} +1 -1
- package/fixtures/{anti-slop/no-known-value-widening.fixture.ts → no-known-value-widening.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-module-mocking.fixture.ts → no-module-mocking.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-object-parameters.fixture.ts → no-object-parameters.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-reduce-accumulator-copy.fixture.ts → no-reduce-accumulator-copy.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-reflect-apply.fixture.ts → no-reflect-apply.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-reflect-get.fixture.ts → no-reflect-get.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-runtime-typeof.fixture.ts → no-runtime-typeof.fixture.ts} +1 -1
- package/fixtures/no-shape-in-symbol-names.fixture.ts +2 -0
- package/fixtures/{anti-slop/no-unknown-parameters.fixture.ts → no-unknown-parameters.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-unknown-returns.fixture.ts → no-unknown-returns.fixture.ts} +1 -1
- package/fixtures/no-unknown-type-aliases.fixture.ts +2 -0
- package/fixtures/no-unsafe-dictionary-type.fixture.ts +2 -0
- package/fixtures/{react/no-use-effect.fixture.tsx → no-use-effect.fixture.tsx} +1 -1
- package/fixtures/{anti-slop/no-widen-then-assert.fixture.ts → no-widen-then-assert.fixture.ts} +1 -1
- package/fixtures/{anti-slop/require-safety-comment-for-type-assertion.fixture.ts → require-safety-comment-for-type-assertion.fixture.ts} +1 -1
- package/fixtures/{expo/static-public-env.fixture.ts → static-public-env.fixture.ts} +1 -1
- package/package.json +26 -26
- package/dist/anti-slop.d.mts +0 -6
- package/dist/anti-slop.mjs.map +0 -1
- package/dist/expo.d.mts +0 -5
- package/dist/expo.mjs +0 -51
- package/dist/expo.mjs.map +0 -1
- package/dist/i18n.d.mts +0 -5
- package/dist/i18n.mjs +0 -50
- package/dist/i18n.mjs.map +0 -1
- package/dist/react.d.mts +0 -5
- package/dist/react.mjs +0 -73
- package/dist/react.mjs.map +0 -1
- package/dist/rule-options-DrAXz8BD.mjs +0 -18
- package/dist/rule-options-DrAXz8BD.mjs.map +0 -1
- package/fixtures/anti-slop/no-shape-in-symbol-names.fixture.ts +0 -2
- package/fixtures/anti-slop/no-unknown-type-aliases.fixture.ts +0 -2
- package/fixtures/anti-slop/no-unsafe-dictionary-type.fixture.ts +0 -2
package/dist/i18n.mjs
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { t as stringOption } from "./rule-options-DrAXz8BD.mjs";
|
|
2
|
-
import { definePlugin, defineRule } from "@oxlint/plugins";
|
|
3
|
-
//#region src/i18n/rules/no-hardcoded-jsx-text.ts
|
|
4
|
-
const defaultWordPattern = String.raw`\p{L}{3,}`;
|
|
5
|
-
/** A string literal that is the whole expression of `{...}` placed among an element's children. */
|
|
6
|
-
function isChildStringLiteral(node) {
|
|
7
|
-
const { expression, parent } = node;
|
|
8
|
-
if (expression.type !== "Literal" || typeof expression.value !== "string") return false;
|
|
9
|
-
return parent.type === "JSXElement" || parent.type === "JSXFragment";
|
|
10
|
-
}
|
|
11
|
-
/** Reject words written straight into JSX instead of coming from the translation dictionary. */
|
|
12
|
-
const noHardcodedJsxTextRule = defineRule({
|
|
13
|
-
meta: {
|
|
14
|
-
type: "problem",
|
|
15
|
-
docs: { description: "Disallow literal words in JSX children; every string on screen comes from the translation dictionary." },
|
|
16
|
-
messages: { hardcodedText: "Do not write words straight into JSX. Every string on screen comes from the translation dictionary." },
|
|
17
|
-
schema: [{
|
|
18
|
-
type: "object",
|
|
19
|
-
properties: { pattern: { type: "string" } },
|
|
20
|
-
additionalProperties: false
|
|
21
|
-
}]
|
|
22
|
-
},
|
|
23
|
-
create(context) {
|
|
24
|
-
const word = new RegExp(stringOption(context.options, "pattern") ?? defaultWordPattern, "u");
|
|
25
|
-
return {
|
|
26
|
-
JSXText(node) {
|
|
27
|
-
if (word.test(node.value)) context.report({
|
|
28
|
-
node,
|
|
29
|
-
messageId: "hardcodedText"
|
|
30
|
-
});
|
|
31
|
-
},
|
|
32
|
-
JSXExpressionContainer(node) {
|
|
33
|
-
if (isChildStringLiteral(node) && word.test(node.expression.value)) context.report({
|
|
34
|
-
node: node.expression,
|
|
35
|
-
messageId: "hardcodedText"
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
//#endregion
|
|
42
|
-
//#region src/i18n/index.ts
|
|
43
|
-
const i18nPlugin = definePlugin({
|
|
44
|
-
meta: { name: "i18n" },
|
|
45
|
-
rules: { "no-hardcoded-jsx-text": noHardcodedJsxTextRule }
|
|
46
|
-
});
|
|
47
|
-
//#endregion
|
|
48
|
-
export { i18nPlugin as default };
|
|
49
|
-
|
|
50
|
-
//# sourceMappingURL=i18n.mjs.map
|
package/dist/i18n.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.mjs","names":[],"sources":["../src/i18n/rules/no-hardcoded-jsx-text.ts","../src/i18n/index.ts"],"sourcesContent":["import { defineRule } from \"@oxlint/plugins\";\n\nimport type { ESTree } from \"@oxlint/plugins\";\n\nimport { stringOption } from \"../../shared/rule-options.ts\";\n\n// Three letters in a row, in any script. Digits, separators and bullets are not words.\nconst defaultWordPattern = String.raw`\\p{L}{3,}`;\n\n/** A string literal that is the whole expression of `{...}` placed among an element's children. */\nfunction isChildStringLiteral(\n node: ESTree.JSXExpressionContainer,\n): node is ESTree.JSXExpressionContainer & {\n readonly expression: ESTree.StringLiteral;\n} {\n const { expression, parent } = node;\n if (expression.type !== \"Literal\" || typeof expression.value !== \"string\") return false;\n return parent.type === \"JSXElement\" || parent.type === \"JSXFragment\";\n}\n\n/** Reject words written straight into JSX instead of coming from the translation dictionary. */\nexport const noHardcodedJsxTextRule = defineRule({\n meta: {\n type: \"problem\",\n docs: {\n description:\n \"Disallow literal words in JSX children; every string on screen comes from the translation dictionary.\",\n },\n messages: {\n hardcodedText:\n \"Do not write words straight into JSX. Every string on screen comes from the translation dictionary.\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n pattern: { type: \"string\" },\n },\n additionalProperties: false,\n },\n ],\n },\n create(context) {\n const word = new RegExp(stringOption(context.options, \"pattern\") ?? defaultWordPattern, \"u\");\n return {\n JSXText(node) {\n if (word.test(node.value)) context.report({ node, messageId: \"hardcodedText\" });\n },\n JSXExpressionContainer(node) {\n if (isChildStringLiteral(node) && word.test(node.expression.value)) {\n context.report({ node: node.expression, messageId: \"hardcodedText\" });\n }\n },\n };\n },\n});\n","import { definePlugin } from \"@oxlint/plugins\";\n\nimport { noHardcodedJsxTextRule } from \"./rules/no-hardcoded-jsx-text.ts\";\n\nconst i18nPlugin = definePlugin({\n meta: { name: \"i18n\" },\n rules: {\n \"no-hardcoded-jsx-text\": noHardcodedJsxTextRule,\n },\n});\n\nexport default i18nPlugin;\n"],"mappings":";;;AAOA,MAAM,qBAAqB,OAAO,GAAG;;AAGrC,SAAS,qBACP,MAGA;CACA,MAAM,EAAE,YAAY,WAAW;CAC/B,IAAI,WAAW,SAAS,aAAa,OAAO,WAAW,UAAU,UAAU,OAAO;CAClF,OAAO,OAAO,SAAS,gBAAgB,OAAO,SAAS;AACzD;;AAGA,MAAa,yBAAyB,WAAW;CAC/C,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,wGACJ;EACA,UAAU,EACR,eACE,sGACJ;EACA,QAAQ,CACN;GACE,MAAM;GACN,YAAY,EACV,SAAS,EAAE,MAAM,SAAS,EAC5B;GACA,sBAAsB;EACxB,CACF;CACF;CACA,OAAO,SAAS;EACd,MAAM,OAAO,IAAI,OAAO,aAAa,QAAQ,SAAS,SAAS,KAAK,oBAAoB,GAAG;EAC3F,OAAO;GACL,QAAQ,MAAM;IACZ,IAAI,KAAK,KAAK,KAAK,KAAK,GAAG,QAAQ,OAAO;KAAE;KAAM,WAAW;IAAgB,CAAC;GAChF;GACA,uBAAuB,MAAM;IAC3B,IAAI,qBAAqB,IAAI,KAAK,KAAK,KAAK,KAAK,WAAW,KAAK,GAC/D,QAAQ,OAAO;KAAE,MAAM,KAAK;KAAY,WAAW;IAAgB,CAAC;GAExE;EACF;CACF;AACF,CAAC;;;ACnDD,MAAM,aAAa,aAAa;CAC9B,MAAM,EAAE,MAAM,OAAO;CACrB,OAAO,EACL,yBAAyB,uBAC3B;AACF,CAAC"}
|
package/dist/react.d.mts
DELETED
package/dist/react.mjs
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { t as stringOption } from "./rule-options-DrAXz8BD.mjs";
|
|
2
|
-
import { definePlugin, defineRule } from "@oxlint/plugins";
|
|
3
|
-
//#region src/react/rules/no-use-effect.ts
|
|
4
|
-
const effectHooks = /* @__PURE__ */ new Set(["useEffect", "useLayoutEffect"]);
|
|
5
|
-
/** The effect hook a call reaches, whether called bare (`useEffect(...)`) or through a member (`React.useEffect(...)`). */
|
|
6
|
-
function calledEffectHook(callee) {
|
|
7
|
-
if (callee.type === "Identifier") return effectHooks.has(callee.name) ? callee.name : null;
|
|
8
|
-
if (callee.type !== "MemberExpression" || callee.computed) return null;
|
|
9
|
-
const { property } = callee;
|
|
10
|
-
return property.type === "Identifier" && effectHooks.has(property.name) ? property.name : null;
|
|
11
|
-
}
|
|
12
|
-
function importedName(specifier) {
|
|
13
|
-
return specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
|
|
14
|
-
}
|
|
15
|
-
function isTypeOnly(specifier) {
|
|
16
|
-
const declaration = specifier.parent;
|
|
17
|
-
return specifier.importKind === "type" || declaration.type === "ImportDeclaration" && declaration.importKind === "type";
|
|
18
|
-
}
|
|
19
|
-
/** Reject `useEffect` and `useLayoutEffect`, called or imported. */
|
|
20
|
-
const noUseEffectRule = defineRule({
|
|
21
|
-
meta: {
|
|
22
|
-
type: "problem",
|
|
23
|
-
docs: { description: "Disallow useEffect and useLayoutEffect; derive during render, act in the event handler, or subscribe through a reactive query." },
|
|
24
|
-
messages: {
|
|
25
|
-
noEffect: "Do not call {{name}}. Derive the value during render, do the work in the event handler, or subscribe through a reactive query.",
|
|
26
|
-
noEffectWithHint: "Do not call {{name}}. Derive the value during render, do the work in the event handler, or subscribe through a reactive query. {{hint}}"
|
|
27
|
-
},
|
|
28
|
-
schema: [{
|
|
29
|
-
type: "object",
|
|
30
|
-
properties: { hint: { type: "string" } },
|
|
31
|
-
additionalProperties: false
|
|
32
|
-
}]
|
|
33
|
-
},
|
|
34
|
-
create(context) {
|
|
35
|
-
const hint = stringOption(context.options, "hint");
|
|
36
|
-
const report = (node, name) => {
|
|
37
|
-
if (hint === void 0) context.report({
|
|
38
|
-
node,
|
|
39
|
-
messageId: "noEffect",
|
|
40
|
-
data: { name }
|
|
41
|
-
});
|
|
42
|
-
else context.report({
|
|
43
|
-
node,
|
|
44
|
-
messageId: "noEffectWithHint",
|
|
45
|
-
data: {
|
|
46
|
-
name,
|
|
47
|
-
hint
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
};
|
|
51
|
-
return {
|
|
52
|
-
CallExpression(node) {
|
|
53
|
-
const name = calledEffectHook(node.callee);
|
|
54
|
-
if (name !== null) report(node, name);
|
|
55
|
-
},
|
|
56
|
-
ImportSpecifier(node) {
|
|
57
|
-
if (isTypeOnly(node)) return;
|
|
58
|
-
const name = importedName(node);
|
|
59
|
-
if (effectHooks.has(name)) report(node, name);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
//#endregion
|
|
65
|
-
//#region src/react/index.ts
|
|
66
|
-
const reactPolicyPlugin = definePlugin({
|
|
67
|
-
meta: { name: "react-policy" },
|
|
68
|
-
rules: { "no-use-effect": noUseEffectRule }
|
|
69
|
-
});
|
|
70
|
-
//#endregion
|
|
71
|
-
export { reactPolicyPlugin as default };
|
|
72
|
-
|
|
73
|
-
//# sourceMappingURL=react.mjs.map
|
package/dist/react.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"react.mjs","names":[],"sources":["../src/react/rules/no-use-effect.ts","../src/react/index.ts"],"sourcesContent":["import { defineRule } from \"@oxlint/plugins\";\n\nimport type { ESTree } from \"@oxlint/plugins\";\n\nimport { stringOption } from \"../../shared/rule-options.ts\";\n\nconst effectHooks = new Set([\"useEffect\", \"useLayoutEffect\"]);\n\n/** The effect hook a call reaches, whether called bare (`useEffect(...)`) or through a member (`React.useEffect(...)`). */\nfunction calledEffectHook(callee: ESTree.Expression): string | null {\n if (callee.type === \"Identifier\") return effectHooks.has(callee.name) ? callee.name : null;\n if (callee.type !== \"MemberExpression\" || callee.computed) return null;\n const { property } = callee;\n return property.type === \"Identifier\" && effectHooks.has(property.name) ? property.name : null;\n}\n\nfunction importedName(specifier: ESTree.ImportSpecifier): string {\n return specifier.imported.type === \"Identifier\"\n ? specifier.imported.name\n : specifier.imported.value;\n}\n\nfunction isTypeOnly(specifier: ESTree.ImportSpecifier): boolean {\n const declaration = specifier.parent;\n return (\n specifier.importKind === \"type\" ||\n (declaration.type === \"ImportDeclaration\" && declaration.importKind === \"type\")\n );\n}\n\n/** Reject `useEffect` and `useLayoutEffect`, called or imported. */\nexport const noUseEffectRule = defineRule({\n meta: {\n type: \"problem\",\n docs: {\n description:\n \"Disallow useEffect and useLayoutEffect; derive during render, act in the event handler, or subscribe through a reactive query.\",\n },\n messages: {\n noEffect:\n \"Do not call {{name}}. Derive the value during render, do the work in the event handler, or subscribe through a reactive query.\",\n noEffectWithHint:\n \"Do not call {{name}}. Derive the value during render, do the work in the event handler, or subscribe through a reactive query. {{hint}}\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n hint: { type: \"string\" },\n },\n additionalProperties: false,\n },\n ],\n },\n create(context) {\n const hint = stringOption(context.options, \"hint\");\n const report = (node: ESTree.Node, name: string): void => {\n if (hint === undefined) {\n context.report({ node, messageId: \"noEffect\", data: { name } });\n } else {\n context.report({ node, messageId: \"noEffectWithHint\", data: { name, hint } });\n }\n };\n return {\n CallExpression(node) {\n const name = calledEffectHook(node.callee);\n if (name !== null) report(node, name);\n },\n ImportSpecifier(node) {\n if (isTypeOnly(node)) return;\n const name = importedName(node);\n if (effectHooks.has(name)) report(node, name);\n },\n };\n },\n});\n","import { definePlugin } from \"@oxlint/plugins\";\n\nimport { noUseEffectRule } from \"./rules/no-use-effect.ts\";\n\n// Named `react-policy`, not `react`: oxlint has a built-in plugin called `react`\n// and a JS plugin of the same name would collide with it.\nconst reactPolicyPlugin = definePlugin({\n meta: { name: \"react-policy\" },\n rules: {\n \"no-use-effect\": noUseEffectRule,\n },\n});\n\nexport default reactPolicyPlugin;\n"],"mappings":";;;AAMA,MAAM,8BAAc,IAAI,IAAI,CAAC,aAAa,iBAAiB,CAAC;;AAG5D,SAAS,iBAAiB,QAA0C;CAClE,IAAI,OAAO,SAAS,cAAc,OAAO,YAAY,IAAI,OAAO,IAAI,IAAI,OAAO,OAAO;CACtF,IAAI,OAAO,SAAS,sBAAsB,OAAO,UAAU,OAAO;CAClE,MAAM,EAAE,aAAa;CACrB,OAAO,SAAS,SAAS,gBAAgB,YAAY,IAAI,SAAS,IAAI,IAAI,SAAS,OAAO;AAC5F;AAEA,SAAS,aAAa,WAA2C;CAC/D,OAAO,UAAU,SAAS,SAAS,eAC/B,UAAU,SAAS,OACnB,UAAU,SAAS;AACzB;AAEA,SAAS,WAAW,WAA4C;CAC9D,MAAM,cAAc,UAAU;CAC9B,OACE,UAAU,eAAe,UACxB,YAAY,SAAS,uBAAuB,YAAY,eAAe;AAE5E;;AAGA,MAAa,kBAAkB,WAAW;CACxC,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,iIACJ;EACA,UAAU;GACR,UACE;GACF,kBACE;EACJ;EACA,QAAQ,CACN;GACE,MAAM;GACN,YAAY,EACV,MAAM,EAAE,MAAM,SAAS,EACzB;GACA,sBAAsB;EACxB,CACF;CACF;CACA,OAAO,SAAS;EACd,MAAM,OAAO,aAAa,QAAQ,SAAS,MAAM;EACjD,MAAM,UAAU,MAAmB,SAAuB;GACxD,IAAI,SAAS,KAAA,GACX,QAAQ,OAAO;IAAE;IAAM,WAAW;IAAY,MAAM,EAAE,KAAK;GAAE,CAAC;QAE9D,QAAQ,OAAO;IAAE;IAAM,WAAW;IAAoB,MAAM;KAAE;KAAM;IAAK;GAAE,CAAC;EAEhF;EACA,OAAO;GACL,eAAe,MAAM;IACnB,MAAM,OAAO,iBAAiB,KAAK,MAAM;IACzC,IAAI,SAAS,MAAM,OAAO,MAAM,IAAI;GACtC;GACA,gBAAgB,MAAM;IACpB,IAAI,WAAW,IAAI,GAAG;IACtB,MAAM,OAAO,aAAa,IAAI;IAC9B,IAAI,YAAY,IAAI,IAAI,GAAG,OAAO,MAAM,IAAI;GAC9C;EACF;CACF;AACF,CAAC;;;ACrED,MAAM,oBAAoB,aAAa;CACrC,MAAM,EAAE,MAAM,eAAe;CAC7B,OAAO,EACL,iBAAiB,gBACnB;AACF,CAAC"}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
//#region src/shared/rule-options.ts
|
|
2
|
-
/**
|
|
3
|
-
* Rule options arrive as parsed JSON from the linter config, so this is the one
|
|
4
|
-
* place their shape is checked. Returns the first option when it is an object.
|
|
5
|
-
*/
|
|
6
|
-
function firstOptionObject(options) {
|
|
7
|
-
const first = options[0];
|
|
8
|
-
return typeof first === "object" && first !== null && !Array.isArray(first) ? first : {};
|
|
9
|
-
}
|
|
10
|
-
/** A string-valued key of the first option object, or undefined when absent or not a string. */
|
|
11
|
-
function stringOption(options, key) {
|
|
12
|
-
const value = firstOptionObject(options)[key];
|
|
13
|
-
return typeof value === "string" ? value : void 0;
|
|
14
|
-
}
|
|
15
|
-
//#endregion
|
|
16
|
-
export { stringOption as t };
|
|
17
|
-
|
|
18
|
-
//# sourceMappingURL=rule-options-DrAXz8BD.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rule-options-DrAXz8BD.mjs","names":[],"sources":["../src/shared/rule-options.ts"],"sourcesContent":["import type { Options } from \"@oxlint/plugins\";\n\ntype OptionValue = Options[number];\n\n/**\n * Rule options arrive as parsed JSON from the linter config, so this is the one\n * place their shape is checked. Returns the first option when it is an object.\n */\nfunction firstOptionObject(options: Readonly<Options>): { readonly [key: string]: OptionValue } {\n const first = options[0];\n return typeof first === \"object\" && first !== null && !Array.isArray(first) ? first : {};\n}\n\n/** A string-valued key of the first option object, or undefined when absent or not a string. */\nexport function stringOption(options: Readonly<Options>, key: string): string | undefined {\n const value = firstOptionObject(options)[key];\n return typeof value === \"string\" ? value : undefined;\n}\n"],"mappings":";;;;;AAQA,SAAS,kBAAkB,SAAqE;CAC9F,MAAM,QAAQ,QAAQ;CACtB,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC;AACzF;;AAGA,SAAgB,aAAa,SAA4B,KAAiC;CACxF,MAAM,QAAQ,kBAAkB,OAAO,CAAC,CAAC;CACzC,OAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;AAC7C"}
|