vite-plugin-comment-attrs 0.0.0 → 0.0.1

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/dist/index.js CHANGED
@@ -54,7 +54,9 @@ function parseDirectiveText(text, directives) {
54
54
  const trimmed = text.trim();
55
55
  for (const [directive, rule] of Object.entries(directives)) {
56
56
  const escapedDirective = escapeRegExp(directive);
57
- const match = trimmed.match(new RegExp(`^${escapedDirective}\\s+(.+)$`));
57
+ const match = trimmed.match(
58
+ new RegExp(`^${escapedDirective}(?:\\s+(.+))?$`)
59
+ );
58
60
  if (!match) {
59
61
  continue;
60
62
  }
@@ -62,7 +64,7 @@ function parseDirectiveText(text, directives) {
62
64
  directive,
63
65
  attrName: rule.attr,
64
66
  merge: rule.merge,
65
- value: match[1].trim()
67
+ value: match[1]?.trim() ?? ""
66
68
  };
67
69
  }
68
70
  return null;
@@ -125,6 +127,9 @@ function applyAttributes(openingElement, attrs) {
125
127
  setOrMergeAttribute(openingElement, attrName, value, collected.merge);
126
128
  }
127
129
  }
130
+ function isJsxCommentOnlyExpression(node) {
131
+ return t.isJSXExpressionContainer(node) && t.isJSXEmptyExpression(node.expression);
132
+ }
128
133
  function processJsxChildren(children, directives) {
129
134
  for (let i = 0; i < children.length; i++) {
130
135
  const firstDirective = getJsxDirectiveValue(children[i], directives);
@@ -148,6 +153,10 @@ function processJsxChildren(children, directives) {
148
153
  j++;
149
154
  continue;
150
155
  }
156
+ if (isJsxCommentOnlyExpression(child)) {
157
+ j++;
158
+ continue;
159
+ }
151
160
  if (t.isJSXElement(child)) {
152
161
  applyAttributes(child.openingElement, attrs);
153
162
  for (let k = removeIndexes.length - 1; k >= 0; k--) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\nimport { parse } from \"@babel/parser\";\nimport traverseModule from \"@babel/traverse\";\nimport type { NodePath } from \"@babel/traverse\";\nimport generateModule from \"@babel/generator\";\nimport * as t from \"@babel/types\";\n\nfunction interopDefault<T extends object>(module: T | { default: T }): T {\n return \"default\" in module ? module.default : module;\n}\n\nconst traverse = interopDefault(traverseModule);\nconst generate = interopDefault(generateModule);\n\nexport type MergeStrategy = \"append\" | \"replace\";\n\nexport type DirectiveRule = {\n attr: string;\n merge: MergeStrategy;\n};\n\nexport type DirectiveConfig = Record<string, DirectiveRule>;\n\nexport type CommentAttrsPluginOptions = {\n directives?: DirectiveConfig;\n include?: RegExp;\n exclude?: RegExp;\n};\n\ntype ParsedDirective = {\n directive: string;\n attrName: string;\n merge: MergeStrategy;\n value: string;\n};\n\ntype CollectedAttr = {\n merge: MergeStrategy;\n values: string[];\n};\n\nconst DEFAULT_DIRECTIVES: DirectiveConfig = {\n \"@class\": { attr: \"class\", merge: \"append\" },\n};\n\nfunction isTargetFile(id: string, options: CommentAttrsPluginOptions) {\n const cleanId = id.split(\"?\")[0];\n\n if (options.exclude?.test(cleanId)) {\n return false;\n }\n\n if (options.include) {\n return options.include.test(cleanId);\n }\n\n return /\\.(jsx|tsx|js|ts)$/.test(cleanId);\n}\n\nfunction escapeRegExp(value: string) {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction codeContainsAnyDirective(code: string, directives: DirectiveConfig) {\n return Object.keys(directives).some((directive) => code.includes(directive));\n}\n\nfunction isWhitespaceJsxText(node: t.Node): boolean {\n return t.isJSXText(node) && node.value.trim() === \"\";\n}\n\nfunction getJsxAttribute(openingElement: t.JSXOpeningElement, name: string) {\n return openingElement.attributes.find((attr) => {\n return t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name, { name });\n }) as t.JSXAttribute | undefined;\n}\n\nfunction getCommentTextsFromJsxNode(node: t.Node): string[] {\n if (!t.isJSXExpressionContainer(node)) {\n return [];\n }\n\n const texts: string[] = [];\n\n if (t.isJSXEmptyExpression(node.expression)) {\n texts.push(\n ...(node.expression.innerComments ?? []).map((comment) => comment.value),\n );\n }\n\n texts.push(...(node.innerComments ?? []).map((comment) => comment.value));\n texts.push(...(node.leadingComments ?? []).map((comment) => comment.value));\n texts.push(...(node.trailingComments ?? []).map((comment) => comment.value));\n\n return texts;\n}\n\nfunction parseDirectiveText(\n text: string,\n directives: DirectiveConfig,\n): ParsedDirective | null {\n const trimmed = text.trim();\n\n for (const [directive, rule] of Object.entries(directives)) {\n const escapedDirective = escapeRegExp(directive);\n const match = trimmed.match(new RegExp(`^${escapedDirective}\\\\s+(.+)$`));\n\n if (!match) {\n continue;\n }\n\n return {\n directive,\n attrName: rule.attr,\n merge: rule.merge,\n value: match[1].trim(),\n };\n }\n\n return null;\n}\n\nfunction getJsxDirectiveValue(\n node: t.Node,\n directives: DirectiveConfig,\n): ParsedDirective | null {\n for (const text of getCommentTextsFromJsxNode(node)) {\n const parsed = parseDirectiveText(text, directives);\n\n if (parsed) {\n return parsed;\n }\n }\n\n return null;\n}\n\nfunction addCollectedAttr(\n attrs: Map<string, CollectedAttr>,\n parsed: ParsedDirective,\n) {\n const existing = attrs.get(parsed.attrName);\n\n if (!existing) {\n attrs.set(parsed.attrName, {\n merge: parsed.merge,\n values: [parsed.value],\n });\n return;\n }\n\n if (parsed.merge === \"append\") {\n existing.merge = \"append\";\n existing.values.push(parsed.value);\n return;\n }\n\n existing.merge = \"replace\";\n existing.values = [parsed.value];\n}\n\nfunction setOrMergeAttribute(\n openingElement: t.JSXOpeningElement,\n attrName: string,\n value: string,\n merge: MergeStrategy,\n) {\n const attr = getJsxAttribute(openingElement, attrName);\n\n if (!attr) {\n openingElement.attributes.push(\n t.jsxAttribute(t.jsxIdentifier(attrName), t.stringLiteral(value)),\n );\n return;\n }\n\n if (merge === \"replace\") {\n attr.value = t.stringLiteral(value);\n return;\n }\n\n if (t.isStringLiteral(attr.value)) {\n attr.value.value = `${attr.value.value} ${value}`.trim();\n return;\n }\n\n if (\n t.isJSXExpressionContainer(attr.value) &&\n t.isExpression(attr.value.expression)\n ) {\n attr.value.expression = t.templateLiteral(\n [\n t.templateElement({ raw: \"\", cooked: \"\" }),\n t.templateElement({ raw: ` ${value}`, cooked: ` ${value}` }, true),\n ],\n [attr.value.expression],\n );\n }\n}\n\nfunction applyAttributes(\n openingElement: t.JSXOpeningElement,\n attrs: Map<string, CollectedAttr>,\n) {\n for (const [attrName, collected] of attrs) {\n const value =\n collected.merge === \"append\"\n ? collected.values.join(\" \")\n : collected.values[collected.values.length - 1];\n\n setOrMergeAttribute(openingElement, attrName, value, collected.merge);\n }\n}\n\nfunction processJsxChildren(\n children: t.JSXElement[\"children\"],\n directives: DirectiveConfig,\n) {\n for (let i = 0; i < children.length; i++) {\n const firstDirective = getJsxDirectiveValue(children[i], directives);\n\n if (!firstDirective) {\n continue;\n }\n\n const attrs = new Map<string, CollectedAttr>();\n const removeIndexes: number[] = [i];\n\n addCollectedAttr(attrs, firstDirective);\n\n let j = i + 1;\n\n while (j < children.length) {\n const child = children[j];\n\n if (isWhitespaceJsxText(child)) {\n j++;\n continue;\n }\n\n const nextDirective = getJsxDirectiveValue(child, directives);\n\n if (nextDirective) {\n addCollectedAttr(attrs, nextDirective);\n removeIndexes.push(j);\n j++;\n continue;\n }\n\n if (t.isJSXElement(child)) {\n applyAttributes(child.openingElement, attrs);\n\n for (let k = removeIndexes.length - 1; k >= 0; k--) {\n children.splice(removeIndexes[k], 1);\n }\n\n i--;\n }\n\n break;\n }\n }\n}\n\nfunction processLeadingDirectiveComments(\n element: t.JSXElement,\n directives: DirectiveConfig,\n) {\n const comments = element.leadingComments;\n\n if (!comments || comments.length === 0) {\n return;\n }\n\n const attrs = new Map<string, CollectedAttr>();\n\n for (const comment of comments) {\n const parsed = parseDirectiveText(comment.value, directives);\n\n if (parsed) {\n addCollectedAttr(attrs, parsed);\n }\n }\n\n if (attrs.size === 0) {\n return;\n }\n\n applyAttributes(element.openingElement, attrs);\n\n element.leadingComments = comments.filter((comment) => {\n return parseDirectiveText(comment.value, directives) === null;\n });\n}\n\nexport function commentAttrsPlugin(\n options: CommentAttrsPluginOptions = {},\n): Plugin {\n const directives = options.directives ?? DEFAULT_DIRECTIVES;\n\n return {\n name: \"vite-plugin-comment-attrs\",\n enforce: \"pre\",\n\n transform(code, id) {\n if (!isTargetFile(id, options)) {\n return null;\n }\n\n if (!codeContainsAnyDirective(code, directives)) {\n return null;\n }\n\n const ast = parse(code, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n traverse(ast, {\n JSXElement(path: NodePath<t.JSXElement>) {\n processLeadingDirectiveComments(path.node, directives);\n processJsxChildren(path.node.children, directives);\n },\n\n JSXFragment(path: NodePath<t.JSXFragment>) {\n processJsxChildren(path.node.children, directives);\n },\n });\n\n const output = generate(\n ast,\n {\n sourceMaps: true,\n sourceFileName: id,\n },\n code,\n );\n\n return {\n code: output.code,\n map: output.map,\n };\n },\n };\n}\n\nexport default commentAttrsPlugin;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\nimport { parse } from \"@babel/parser\";\nimport traverseModule from \"@babel/traverse\";\nimport type { NodePath } from \"@babel/traverse\";\nimport generateModule from \"@babel/generator\";\nimport * as t from \"@babel/types\";\n\nfunction interopDefault<T extends object>(module: T | { default: T }): T {\n return \"default\" in module ? module.default : module;\n}\n\nconst traverse = interopDefault(traverseModule);\nconst generate = interopDefault(generateModule);\n\nexport type MergeStrategy = \"append\" | \"replace\";\n\nexport type DirectiveRule = {\n attr: string;\n merge: MergeStrategy;\n};\n\nexport type DirectiveConfig = Record<string, DirectiveRule>;\n\nexport type CommentAttrsPluginOptions = {\n directives?: DirectiveConfig;\n include?: RegExp;\n exclude?: RegExp;\n};\n\ntype ParsedDirective = {\n directive: string;\n attrName: string;\n merge: MergeStrategy;\n value: string;\n};\n\ntype CollectedAttr = {\n merge: MergeStrategy;\n values: string[];\n};\n\nconst DEFAULT_DIRECTIVES: DirectiveConfig = {\n \"@class\": { attr: \"class\", merge: \"append\" },\n};\n\nfunction isTargetFile(id: string, options: CommentAttrsPluginOptions) {\n const cleanId = id.split(\"?\")[0];\n\n if (options.exclude?.test(cleanId)) {\n return false;\n }\n\n if (options.include) {\n return options.include.test(cleanId);\n }\n\n return /\\.(jsx|tsx|js|ts)$/.test(cleanId);\n}\n\nfunction escapeRegExp(value: string) {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction codeContainsAnyDirective(code: string, directives: DirectiveConfig) {\n return Object.keys(directives).some((directive) => code.includes(directive));\n}\n\nfunction isWhitespaceJsxText(node: t.Node): boolean {\n return t.isJSXText(node) && node.value.trim() === \"\";\n}\n\nfunction getJsxAttribute(openingElement: t.JSXOpeningElement, name: string) {\n return openingElement.attributes.find((attr) => {\n return t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name, { name });\n }) as t.JSXAttribute | undefined;\n}\n\nfunction getCommentTextsFromJsxNode(node: t.Node): string[] {\n if (!t.isJSXExpressionContainer(node)) {\n return [];\n }\n\n const texts: string[] = [];\n\n if (t.isJSXEmptyExpression(node.expression)) {\n texts.push(\n ...(node.expression.innerComments ?? []).map((comment) => comment.value),\n );\n }\n\n texts.push(...(node.innerComments ?? []).map((comment) => comment.value));\n texts.push(...(node.leadingComments ?? []).map((comment) => comment.value));\n texts.push(...(node.trailingComments ?? []).map((comment) => comment.value));\n\n return texts;\n}\n\nfunction parseDirectiveText(\n text: string,\n directives: DirectiveConfig,\n): ParsedDirective | null {\n const trimmed = text.trim();\n\n for (const [directive, rule] of Object.entries(directives)) {\n const escapedDirective = escapeRegExp(directive);\n\n // const match = trimmed.match(new RegExp(`^${escapedDirective}\\\\s+(.+)$`));\n // this support directive without value\n const match = trimmed.match(\n new RegExp(`^${escapedDirective}(?:\\\\s+(.+))?$`),\n );\n\n if (!match) {\n continue;\n }\n\n return {\n directive,\n attrName: rule.attr,\n merge: rule.merge,\n value: match[1]?.trim() ?? \"\",\n };\n }\n\n return null;\n}\n\nfunction getJsxDirectiveValue(\n node: t.Node,\n directives: DirectiveConfig,\n): ParsedDirective | null {\n for (const text of getCommentTextsFromJsxNode(node)) {\n const parsed = parseDirectiveText(text, directives);\n\n if (parsed) {\n return parsed;\n }\n }\n\n return null;\n}\n\nfunction addCollectedAttr(\n attrs: Map<string, CollectedAttr>,\n parsed: ParsedDirective,\n) {\n const existing = attrs.get(parsed.attrName);\n\n if (!existing) {\n attrs.set(parsed.attrName, {\n merge: parsed.merge,\n values: [parsed.value],\n });\n return;\n }\n\n if (parsed.merge === \"append\") {\n existing.merge = \"append\";\n existing.values.push(parsed.value);\n return;\n }\n\n existing.merge = \"replace\";\n existing.values = [parsed.value];\n}\n\nfunction setOrMergeAttribute(\n openingElement: t.JSXOpeningElement,\n attrName: string,\n value: string,\n merge: MergeStrategy,\n) {\n const attr = getJsxAttribute(openingElement, attrName);\n\n if (!attr) {\n openingElement.attributes.push(\n t.jsxAttribute(t.jsxIdentifier(attrName), t.stringLiteral(value)),\n );\n return;\n }\n\n if (merge === \"replace\") {\n attr.value = t.stringLiteral(value);\n return;\n }\n\n if (t.isStringLiteral(attr.value)) {\n attr.value.value = `${attr.value.value} ${value}`.trim();\n return;\n }\n\n if (\n t.isJSXExpressionContainer(attr.value) &&\n t.isExpression(attr.value.expression)\n ) {\n attr.value.expression = t.templateLiteral(\n [\n t.templateElement({ raw: \"\", cooked: \"\" }),\n t.templateElement({ raw: ` ${value}`, cooked: ` ${value}` }, true),\n ],\n [attr.value.expression],\n );\n }\n}\n\nfunction applyAttributes(\n openingElement: t.JSXOpeningElement,\n attrs: Map<string, CollectedAttr>,\n) {\n for (const [attrName, collected] of attrs) {\n const value =\n collected.merge === \"append\"\n ? collected.values.join(\" \")\n : collected.values[collected.values.length - 1];\n\n setOrMergeAttribute(openingElement, attrName, value, collected.merge);\n }\n}\nfunction isJsxCommentOnlyExpression(node: t.Node): boolean {\n return (\n t.isJSXExpressionContainer(node) && t.isJSXEmptyExpression(node.expression)\n );\n}\nfunction processJsxChildren(\n children: t.JSXElement[\"children\"],\n directives: DirectiveConfig,\n) {\n for (let i = 0; i < children.length; i++) {\n const firstDirective = getJsxDirectiveValue(children[i], directives);\n\n if (!firstDirective) {\n continue;\n }\n\n const attrs = new Map<string, CollectedAttr>();\n const removeIndexes: number[] = [i];\n\n addCollectedAttr(attrs, firstDirective);\n\n let j = i + 1;\n\n while (j < children.length) {\n const child = children[j];\n\n if (isWhitespaceJsxText(child)) {\n j++;\n continue;\n }\n\n const nextDirective = getJsxDirectiveValue(child, directives);\n\n if (nextDirective) {\n addCollectedAttr(attrs, nextDirective);\n removeIndexes.push(j);\n j++;\n continue;\n }\n\n // Skip unrelated JSX comments between directives and the target element.\n // for example @alt and @class\n if (isJsxCommentOnlyExpression(child)) {\n j++;\n continue;\n }\n\n if (t.isJSXElement(child)) {\n applyAttributes(child.openingElement, attrs);\n\n for (let k = removeIndexes.length - 1; k >= 0; k--) {\n children.splice(removeIndexes[k], 1);\n }\n\n i--;\n }\n\n break;\n }\n }\n}\n\nfunction processLeadingDirectiveComments(\n element: t.JSXElement,\n directives: DirectiveConfig,\n) {\n const comments = element.leadingComments;\n\n if (!comments || comments.length === 0) {\n return;\n }\n\n const attrs = new Map<string, CollectedAttr>();\n\n for (const comment of comments) {\n const parsed = parseDirectiveText(comment.value, directives);\n\n if (parsed) {\n addCollectedAttr(attrs, parsed);\n }\n }\n\n if (attrs.size === 0) {\n return;\n }\n\n applyAttributes(element.openingElement, attrs);\n\n element.leadingComments = comments.filter((comment) => {\n return parseDirectiveText(comment.value, directives) === null;\n });\n}\n\nexport function commentAttrsPlugin(\n options: CommentAttrsPluginOptions = {},\n): Plugin {\n const directives = options.directives ?? DEFAULT_DIRECTIVES;\n\n return {\n name: \"vite-plugin-comment-attrs\",\n enforce: \"pre\",\n\n transform(code, id) {\n if (!isTargetFile(id, options)) {\n return null;\n }\n\n if (!codeContainsAnyDirective(code, directives)) {\n return null;\n }\n\n const ast = parse(code, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n traverse(ast, {\n JSXElement(path: NodePath<t.JSXElement>) {\n processLeadingDirectiveComments(path.node, directives);\n processJsxChildren(path.node.children, directives);\n },\n\n JSXFragment(path: NodePath<t.JSXFragment>) {\n processJsxChildren(path.node.children, directives);\n },\n });\n\n const output = generate(\n ast,\n {\n sourceMaps: true,\n sourceFileName: id,\n },\n code,\n );\n\n return {\n code: output.code,\n map: output.map,\n };\n },\n };\n}\n\nexport default commentAttrsPlugin;\n"],"mappings":";AACA,SAAS,aAAa;AACtB,OAAO,oBAAoB;AAE3B,OAAO,oBAAoB;AAC3B,YAAY,OAAO;AAEnB,SAAS,eAAiC,QAA+B;AACvE,SAAO,aAAa,SAAS,OAAO,UAAU;AAChD;AAEA,IAAM,WAAW,eAAe,cAAc;AAC9C,IAAM,WAAW,eAAe,cAAc;AA6B9C,IAAM,qBAAsC;AAAA,EAC1C,UAAU,EAAE,MAAM,SAAS,OAAO,SAAS;AAC7C;AAEA,SAAS,aAAa,IAAY,SAAoC;AACpE,QAAM,UAAU,GAAG,MAAM,GAAG,EAAE,CAAC;AAE/B,MAAI,QAAQ,SAAS,KAAK,OAAO,GAAG;AAClC,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ,SAAS;AACnB,WAAO,QAAQ,QAAQ,KAAK,OAAO;AAAA,EACrC;AAEA,SAAO,qBAAqB,KAAK,OAAO;AAC1C;AAEA,SAAS,aAAa,OAAe;AACnC,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;AAEA,SAAS,yBAAyB,MAAc,YAA6B;AAC3E,SAAO,OAAO,KAAK,UAAU,EAAE,KAAK,CAAC,cAAc,KAAK,SAAS,SAAS,CAAC;AAC7E;AAEA,SAAS,oBAAoB,MAAuB;AAClD,SAAS,YAAU,IAAI,KAAK,KAAK,MAAM,KAAK,MAAM;AACpD;AAEA,SAAS,gBAAgB,gBAAqC,MAAc;AAC1E,SAAO,eAAe,WAAW,KAAK,CAAC,SAAS;AAC9C,WAAS,iBAAe,IAAI,KAAO,kBAAgB,KAAK,MAAM,EAAE,KAAK,CAAC;AAAA,EACxE,CAAC;AACH;AAEA,SAAS,2BAA2B,MAAwB;AAC1D,MAAI,CAAG,2BAAyB,IAAI,GAAG;AACrC,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,QAAkB,CAAC;AAEzB,MAAM,uBAAqB,KAAK,UAAU,GAAG;AAC3C,UAAM;AAAA,MACJ,IAAI,KAAK,WAAW,iBAAiB,CAAC,GAAG,IAAI,CAAC,YAAY,QAAQ,KAAK;AAAA,IACzE;AAAA,EACF;AAEA,QAAM,KAAK,IAAI,KAAK,iBAAiB,CAAC,GAAG,IAAI,CAAC,YAAY,QAAQ,KAAK,CAAC;AACxE,QAAM,KAAK,IAAI,KAAK,mBAAmB,CAAC,GAAG,IAAI,CAAC,YAAY,QAAQ,KAAK,CAAC;AAC1E,QAAM,KAAK,IAAI,KAAK,oBAAoB,CAAC,GAAG,IAAI,CAAC,YAAY,QAAQ,KAAK,CAAC;AAE3E,SAAO;AACT;AAEA,SAAS,mBACP,MACA,YACwB;AACxB,QAAM,UAAU,KAAK,KAAK;AAE1B,aAAW,CAAC,WAAW,IAAI,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC1D,UAAM,mBAAmB,aAAa,SAAS;AAI/C,UAAM,QAAQ,QAAQ;AAAA,MACpB,IAAI,OAAO,IAAI,gBAAgB,gBAAgB;AAAA,IACjD;AAEA,QAAI,CAAC,OAAO;AACV;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,MACA,UAAU,KAAK;AAAA,MACf,OAAO,KAAK;AAAA,MACZ,OAAO,MAAM,CAAC,GAAG,KAAK,KAAK;AAAA,IAC7B;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,qBACP,MACA,YACwB;AACxB,aAAW,QAAQ,2BAA2B,IAAI,GAAG;AACnD,UAAM,SAAS,mBAAmB,MAAM,UAAU;AAElD,QAAI,QAAQ;AACV,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,iBACP,OACA,QACA;AACA,QAAM,WAAW,MAAM,IAAI,OAAO,QAAQ;AAE1C,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,OAAO,UAAU;AAAA,MACzB,OAAO,OAAO;AAAA,MACd,QAAQ,CAAC,OAAO,KAAK;AAAA,IACvB,CAAC;AACD;AAAA,EACF;AAEA,MAAI,OAAO,UAAU,UAAU;AAC7B,aAAS,QAAQ;AACjB,aAAS,OAAO,KAAK,OAAO,KAAK;AACjC;AAAA,EACF;AAEA,WAAS,QAAQ;AACjB,WAAS,SAAS,CAAC,OAAO,KAAK;AACjC;AAEA,SAAS,oBACP,gBACA,UACA,OACA,OACA;AACA,QAAM,OAAO,gBAAgB,gBAAgB,QAAQ;AAErD,MAAI,CAAC,MAAM;AACT,mBAAe,WAAW;AAAA,MACtB,eAAe,gBAAc,QAAQ,GAAK,gBAAc,KAAK,CAAC;AAAA,IAClE;AACA;AAAA,EACF;AAEA,MAAI,UAAU,WAAW;AACvB,SAAK,QAAU,gBAAc,KAAK;AAClC;AAAA,EACF;AAEA,MAAM,kBAAgB,KAAK,KAAK,GAAG;AACjC,SAAK,MAAM,QAAQ,GAAG,KAAK,MAAM,KAAK,IAAI,KAAK,GAAG,KAAK;AACvD;AAAA,EACF;AAEA,MACI,2BAAyB,KAAK,KAAK,KACnC,eAAa,KAAK,MAAM,UAAU,GACpC;AACA,SAAK,MAAM,aAAe;AAAA,MACxB;AAAA,QACI,kBAAgB,EAAE,KAAK,IAAI,QAAQ,GAAG,CAAC;AAAA,QACvC,kBAAgB,EAAE,KAAK,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,GAAG,GAAG,IAAI;AAAA,MACnE;AAAA,MACA,CAAC,KAAK,MAAM,UAAU;AAAA,IACxB;AAAA,EACF;AACF;AAEA,SAAS,gBACP,gBACA,OACA;AACA,aAAW,CAAC,UAAU,SAAS,KAAK,OAAO;AACzC,UAAM,QACJ,UAAU,UAAU,WAChB,UAAU,OAAO,KAAK,GAAG,IACzB,UAAU,OAAO,UAAU,OAAO,SAAS,CAAC;AAElD,wBAAoB,gBAAgB,UAAU,OAAO,UAAU,KAAK;AAAA,EACtE;AACF;AACA,SAAS,2BAA2B,MAAuB;AACzD,SACI,2BAAyB,IAAI,KAAO,uBAAqB,KAAK,UAAU;AAE9E;AACA,SAAS,mBACP,UACA,YACA;AACA,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAM,iBAAiB,qBAAqB,SAAS,CAAC,GAAG,UAAU;AAEnE,QAAI,CAAC,gBAAgB;AACnB;AAAA,IACF;AAEA,UAAM,QAAQ,oBAAI,IAA2B;AAC7C,UAAM,gBAA0B,CAAC,CAAC;AAElC,qBAAiB,OAAO,cAAc;AAEtC,QAAI,IAAI,IAAI;AAEZ,WAAO,IAAI,SAAS,QAAQ;AAC1B,YAAM,QAAQ,SAAS,CAAC;AAExB,UAAI,oBAAoB,KAAK,GAAG;AAC9B;AACA;AAAA,MACF;AAEA,YAAM,gBAAgB,qBAAqB,OAAO,UAAU;AAE5D,UAAI,eAAe;AACjB,yBAAiB,OAAO,aAAa;AACrC,sBAAc,KAAK,CAAC;AACpB;AACA;AAAA,MACF;AAIA,UAAI,2BAA2B,KAAK,GAAG;AACrC;AACA;AAAA,MACF;AAEA,UAAM,eAAa,KAAK,GAAG;AACzB,wBAAgB,MAAM,gBAAgB,KAAK;AAE3C,iBAAS,IAAI,cAAc,SAAS,GAAG,KAAK,GAAG,KAAK;AAClD,mBAAS,OAAO,cAAc,CAAC,GAAG,CAAC;AAAA,QACrC;AAEA;AAAA,MACF;AAEA;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,gCACP,SACA,YACA;AACA,QAAM,WAAW,QAAQ;AAEzB,MAAI,CAAC,YAAY,SAAS,WAAW,GAAG;AACtC;AAAA,EACF;AAEA,QAAM,QAAQ,oBAAI,IAA2B;AAE7C,aAAW,WAAW,UAAU;AAC9B,UAAM,SAAS,mBAAmB,QAAQ,OAAO,UAAU;AAE3D,QAAI,QAAQ;AACV,uBAAiB,OAAO,MAAM;AAAA,IAChC;AAAA,EACF;AAEA,MAAI,MAAM,SAAS,GAAG;AACpB;AAAA,EACF;AAEA,kBAAgB,QAAQ,gBAAgB,KAAK;AAE7C,UAAQ,kBAAkB,SAAS,OAAO,CAAC,YAAY;AACrD,WAAO,mBAAmB,QAAQ,OAAO,UAAU,MAAM;AAAA,EAC3D,CAAC;AACH;AAEO,SAAS,mBACd,UAAqC,CAAC,GAC9B;AACR,QAAM,aAAa,QAAQ,cAAc;AAEzC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IAET,UAAU,MAAM,IAAI;AAClB,UAAI,CAAC,aAAa,IAAI,OAAO,GAAG;AAC9B,eAAO;AAAA,MACT;AAEA,UAAI,CAAC,yBAAyB,MAAM,UAAU,GAAG;AAC/C,eAAO;AAAA,MACT;AAEA,YAAM,MAAM,MAAM,MAAM;AAAA,QACtB,YAAY;AAAA,QACZ,SAAS,CAAC,OAAO,YAAY;AAAA,MAC/B,CAAC;AAED,eAAS,KAAK;AAAA,QACZ,WAAW,MAA8B;AACvC,0CAAgC,KAAK,MAAM,UAAU;AACrD,6BAAmB,KAAK,KAAK,UAAU,UAAU;AAAA,QACnD;AAAA,QAEA,YAAY,MAA+B;AACzC,6BAAmB,KAAK,KAAK,UAAU,UAAU;AAAA,QACnD;AAAA,MACF,CAAC;AAED,YAAM,SAAS;AAAA,QACb;AAAA,QACA;AAAA,UACE,YAAY;AAAA,UACZ,gBAAgB;AAAA,QAClB;AAAA,QACA;AAAA,MACF;AAEA,aAAO;AAAA,QACL,MAAM,OAAO;AAAA,QACb,KAAK,OAAO;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-comment-attrs",
3
- "version": "0.0.0",
3
+ "version": "0.0.1",
4
4
  "description": "A Vite plugin that turns JSX comments into configurable JSX attributes.",
5
5
  "type": "module",
6
6
  "license": "MIT",