@fluid-topics/ft-eslint 1.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.
@@ -0,0 +1,3 @@
1
+ import type { ESLint } from "eslint";
2
+ declare const plugin: ESLint.Plugin;
3
+ export default plugin;
@@ -0,0 +1,20 @@
1
+ import { quotedAttributesRule } from "./rules/quoted-attributes";
2
+ import { typographyVariantRule } from "./rules/typography-variant";
3
+ import { interfaceNoCommaRule } from "./rules/interface-no-commas";
4
+ const plugin = {
5
+ rules: {
6
+ "quoted-attributes": quotedAttributesRule,
7
+ "typography-variant": typographyVariantRule,
8
+ "interface-no-commas": interfaceNoCommaRule,
9
+ },
10
+ configs: {
11
+ recommended: {
12
+ rules: {
13
+ "ft/quoted-attributes": "error",
14
+ "ft/typography-variant": "warn",
15
+ "ft/interface-no-commas": "error",
16
+ },
17
+ },
18
+ },
19
+ };
20
+ export default plugin;
@@ -0,0 +1,2 @@
1
+ import { Rule } from "eslint";
2
+ export declare const interfaceNoCommaRule: Rule.RuleModule;
@@ -0,0 +1,33 @@
1
+ export const interfaceNoCommaRule = {
2
+ meta: {
3
+ docs: {
4
+ description: "Enforces that interfaces declarations do not use commas",
5
+ recommended: false,
6
+ },
7
+ fixable: "code",
8
+ schema: [],
9
+ messages: {
10
+ forbiddenComma: "Commas are not allowed in interfaces declarations",
11
+ },
12
+ },
13
+ create(context) {
14
+ return {
15
+ TSInterfaceDeclaration(node) {
16
+ for (const element of node.body.body) {
17
+ if (element.type !== "TSPropertySignature") {
18
+ continue;
19
+ }
20
+ const property = element;
21
+ const lastToken = context.sourceCode.getLastToken(property);
22
+ if ((lastToken === null || lastToken === void 0 ? void 0 : lastToken.type) === "Punctuator" && lastToken.value === ",") {
23
+ context.report({
24
+ loc: lastToken.loc,
25
+ messageId: "forbiddenComma",
26
+ fix: fixer => fixer.remove(lastToken),
27
+ });
28
+ }
29
+ }
30
+ },
31
+ };
32
+ },
33
+ };
@@ -0,0 +1,2 @@
1
+ import { Rule } from "eslint";
2
+ export declare const quotedAttributesRule: Rule.RuleModule;
@@ -0,0 +1,84 @@
1
+ /* Inspiried by lit/quoted-expressions */
2
+ export const quotedAttributesRule = {
3
+ meta: {
4
+ docs: {
5
+ description: "Enforces the presence or absence of quotes around expressions",
6
+ recommended: false,
7
+ },
8
+ fixable: "code",
9
+ schema: [],
10
+ messages: {
11
+ alwaysQuote: "Attributes must be quoted inside templates " +
12
+ " (e.g. `foo=\"${bar}\"`)",
13
+ neverQuote: "Properties, events and booleans must not be quoted inside templates " +
14
+ " (e.g. `.foo=${bar}`)",
15
+ },
16
+ },
17
+ create(context) {
18
+ const quotePattern = /=(["'])?$/;
19
+ const attributePattern = /[?@.]\w+=["']?$/;
20
+ function addQuotes(previousQuasi, nextQuasi) {
21
+ return (fixer) => {
22
+ if (!previousQuasi.range || !nextQuasi.range) {
23
+ return [];
24
+ }
25
+ return [
26
+ fixer.insertTextAfterRange([previousQuasi.range[0], previousQuasi.range[1] - 2], "\""),
27
+ fixer.insertTextBeforeRange([nextQuasi.range[0] + 1, nextQuasi.range[1]], "\""),
28
+ ];
29
+ };
30
+ }
31
+ function removeQuotes(previousQuasi, nextQuasi) {
32
+ return (fixer) => {
33
+ if (!previousQuasi.range || !nextQuasi.range) {
34
+ return [];
35
+ }
36
+ return [
37
+ fixer.removeRange([
38
+ previousQuasi.range[1] - 3,
39
+ previousQuasi.range[1] - 2,
40
+ ]),
41
+ fixer.removeRange([
42
+ nextQuasi.range[0] + 1,
43
+ nextQuasi.range[0] + 2,
44
+ ]),
45
+ ];
46
+ };
47
+ }
48
+ return {
49
+ TaggedTemplateExpression: (node) => {
50
+ if (node.type !== "TaggedTemplateExpression"
51
+ || node.tag.type !== "Identifier"
52
+ || node.tag.name !== "html") {
53
+ return;
54
+ }
55
+ for (let i = 0; i < node.quasi.expressions.length; i++) {
56
+ const expression = node.quasi.expressions[i];
57
+ const previousQuasi = node.quasi.quasis[i];
58
+ const nextQuasi = node.quasi.quasis[i + 1];
59
+ const quoteMatch = previousQuasi.value.raw.match(quotePattern);
60
+ if (!quoteMatch) { // we are not at a `foo=` token
61
+ continue;
62
+ }
63
+ const needQuote = previousQuasi.value.raw.match(attributePattern) === null;
64
+ const hasStartQuote = quoteMatch[1] !== undefined;
65
+ const isQuoted = hasStartQuote && nextQuasi.value.raw.startsWith(quoteMatch[1]);
66
+ if (needQuote && !hasStartQuote) {
67
+ context.report({
68
+ node: expression,
69
+ messageId: "alwaysQuote",
70
+ fix: addQuotes(previousQuasi, nextQuasi),
71
+ });
72
+ }
73
+ if (!needQuote && isQuoted) {
74
+ context.report({
75
+ node: expression,
76
+ messageId: "neverQuote",
77
+ fix: removeQuotes(previousQuasi, nextQuasi),
78
+ });
79
+ }
80
+ }
81
+ },
82
+ };
83
+ },
84
+ };
@@ -0,0 +1,2 @@
1
+ import { Rule } from "eslint";
2
+ export declare const typographyVariantRule: Rule.RuleModule;
@@ -0,0 +1,49 @@
1
+ import { TemplateAnalyzer } from "eslint-plugin-lit/lib/template-analyzer";
2
+ import { isExpressionPlaceholder } from "eslint-plugin-lit/lib/util";
3
+ export const typographyVariantRule = {
4
+ meta: {
5
+ docs: {
6
+ description: "Enforces that variant attribute uses enum values",
7
+ recommended: false,
8
+ },
9
+ schema: [],
10
+ messages: {
11
+ mandatoryVariant: "<ft-typography> must have a variant attribute",
12
+ variantUsesEnum: "variant attribute must use an enum value " +
13
+ " (e.g. `variant=\"${ FtTypographyVariants.title1 }\"`)",
14
+ },
15
+ },
16
+ create(context) {
17
+ return {
18
+ TaggedTemplateExpression: (node) => {
19
+ if (node.type !== "TaggedTemplateExpression"
20
+ || node.tag.type !== "Identifier"
21
+ || node.tag.name !== "html") {
22
+ return;
23
+ }
24
+ const analyzer = TemplateAnalyzer.create(node);
25
+ analyzer.traverse({
26
+ enterElement: (element) => {
27
+ if (element.tagName === "ft-typography") {
28
+ if (!("variant" in element.attribs)) {
29
+ const loc = analyzer.resolveLocation(element.sourceCodeLocation, context.sourceCode);
30
+ context.report({
31
+ loc,
32
+ messageId: "mandatoryVariant",
33
+ });
34
+ return;
35
+ }
36
+ if (!isExpressionPlaceholder(element.attribs["variant"])) {
37
+ const loc = analyzer.getLocationForAttribute(element, "variant", context.sourceCode);
38
+ context.report({
39
+ loc,
40
+ messageId: "variantUsesEnum",
41
+ });
42
+ }
43
+ }
44
+ },
45
+ });
46
+ },
47
+ };
48
+ },
49
+ };
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@fluid-topics/ft-eslint",
3
+ "version": "1.3.0",
4
+ "description": "ESlint rules for web components",
5
+ "keywords": [
6
+ "Lit"
7
+ ],
8
+ "author": "Fluid Topics <devtopics@antidot.net>",
9
+ "license": "ISC",
10
+ "main": "build/index.js",
11
+ "web": "build/ft-eslint.min.js",
12
+ "typings": "build/index",
13
+ "files": [
14
+ "build/**/*.js",
15
+ "build/**/*.ts"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "ssh://git@scm.mrs.antidot.net:2222/fluidtopics/ft-web-components.git"
20
+ },
21
+ "dependencies": {
22
+ "lit": "3.1.0"
23
+ },
24
+ "gitHead": "cc982effbf27c0483e03961ad450fd2057a108b2"
25
+ }