rollup-plugin-react-scoped-css 0.4.1 → 0.6.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.
Files changed (41) hide show
  1. package/.eslintrc.yml +29 -0
  2. package/.github/workflows/pull-request.yml +20 -0
  3. package/.github/workflows/release.yml +30 -0
  4. package/.releaserc +13 -0
  5. package/.vscode/settings.json +8 -0
  6. package/README.md +55 -7
  7. package/babel.config.js +6 -2
  8. package/dist/ast-iterator.d.ts +1 -0
  9. package/dist/ast-iterator.js +114 -0
  10. package/dist/ast-transformer.d.ts +1 -0
  11. package/dist/ast-transformer.js +50 -0
  12. package/dist/ast.utils.d.ts +4 -0
  13. package/dist/ast.utils.js +49 -108
  14. package/dist/index.d.ts +2 -2
  15. package/dist/index.js +24 -23
  16. package/dist/parsers/classic-parser.d.ts +3 -2
  17. package/dist/parsers/classic-parser.js +87 -24
  18. package/dist/parsers/jsx-parser.model.d.ts +0 -3
  19. package/dist/parsers/new-parser.d.ts +3 -2
  20. package/dist/parsers/new-parser.js +86 -23
  21. package/package.json +18 -3
  22. package/src/__mocks__/{compiled-jsx-with-attr.js → classic-compiled-jsx-with-attr.js} +0 -0
  23. package/src/__mocks__/{compiled-jsx.js → classic-compiled-jsx.js} +0 -0
  24. package/src/__mocks__/new-compiled-jsx-with-attr.js +101 -0
  25. package/src/__mocks__/new-compiled-jsx.js +64 -0
  26. package/src/__mocks__/sample-code-file.js +4 -0
  27. package/src/ast-iterator.spec.ts +22 -0
  28. package/src/ast-iterator.ts +32 -0
  29. package/src/ast-transformer.spec.ts +23 -0
  30. package/src/ast-transformer.ts +28 -0
  31. package/src/ast.utils.spec.ts +59 -20
  32. package/src/ast.utils.ts +41 -99
  33. package/src/index.ts +77 -57
  34. package/src/parsers/classic-parser.ts +72 -0
  35. package/src/parsers/jsx-parser.model.ts +10 -0
  36. package/src/parsers/new-parser.ts +74 -0
  37. package/tsconfig.json +2 -1
  38. package/dist/parsers/base-parser.d.ts +0 -15
  39. package/dist/parsers/base-parser.js +0 -109
  40. package/dist/parsers/jsx-parser.interface.d.ts +0 -6
  41. package/dist/parsers/jsx-parser.interface.js +0 -2
@@ -0,0 +1,32 @@
1
+ const isNode = (obj: any) => {
2
+ return (
3
+ typeof obj === "object" &&
4
+ typeof (obj as any)?.end === "number" &&
5
+ typeof (obj as any)?.start === "number" &&
6
+ typeof (obj as any)?.type === "string"
7
+ );
8
+ };
9
+
10
+ export const astIterator = function* (ast: any): IterableIterator<any> {
11
+ if (!ast) {
12
+ return;
13
+ }
14
+
15
+ for (const key of Object.keys(ast)) {
16
+ // Skip useless keys
17
+ if (["end", "start", "type"].includes(key)) {
18
+ continue;
19
+ }
20
+
21
+ // If key value is an array, iterate over it
22
+ if (Array.isArray(ast[key])) {
23
+ for (const nodeIndex in ast[key]) {
24
+ yield* astIterator(ast[key][nodeIndex]);
25
+ }
26
+ } else if (isNode(ast[key])) {
27
+ yield* astIterator(ast[key]);
28
+ }
29
+ }
30
+
31
+ yield ast;
32
+ };
@@ -0,0 +1,23 @@
1
+ import { readFile } from "fs/promises";
2
+ import { resolve } from "path";
3
+ import { parse } from "acorn";
4
+ import { astTransformer } from "./ast-transformer";
5
+
6
+ describe("astTransformer", () => {
7
+ it("should iterate through all nodes", async () => {
8
+ let iterationCount = 0;
9
+ const file = await readFile(
10
+ resolve(__dirname, "./__mocks__/sample-code-file.js"),
11
+ { encoding: "utf-8" }
12
+ );
13
+ const ast = parse(file, { sourceType: "module", ecmaVersion: 2022 });
14
+
15
+ const newAst = astTransformer(ast, (node) => {
16
+ iterationCount++;
17
+ expect(node).toBeTruthy();
18
+ });
19
+
20
+ expect(newAst).toBeDefined();
21
+ expect(iterationCount).toBe(28);
22
+ });
23
+ });
@@ -0,0 +1,28 @@
1
+ const isNode = (obj: any) => {
2
+ return (
3
+ typeof obj === "object" &&
4
+ typeof (obj as any)?.end === "number" &&
5
+ typeof (obj as any)?.start === "number" &&
6
+ typeof (obj as any)?.type === "string"
7
+ );
8
+ };
9
+
10
+ export const astTransformer = (ast: any, callback: (ast: any) => any) => {
11
+ for (const key of Object.keys(ast)) {
12
+ if (["end", "start", "type"].includes(key)) {
13
+ continue;
14
+ }
15
+
16
+ if (Array.isArray(ast[key])) {
17
+ for (const nodeIndex in ast[key]) {
18
+ ast[key][nodeIndex] = astTransformer(ast[key][nodeIndex], callback);
19
+ }
20
+ } else if (isNode(ast[key])) {
21
+ ast[key] = astTransformer(ast[key], callback);
22
+ }
23
+ }
24
+
25
+ const result = callback(ast);
26
+
27
+ return typeof result !== "undefined" ? result : ast;
28
+ };
@@ -1,22 +1,61 @@
1
- import { resolve } from 'path'
2
- import { readFile } from 'fs/promises'
3
- import { parse } from 'acorn'
4
- import { generate } from 'escodegen'
5
- import { addHashAttributesToJsxTagsAst } from './ast.utils'
1
+ import { resolve } from "path";
2
+ import { readFile } from "fs/promises";
3
+ import { parse } from "acorn";
4
+ import { generate } from "escodegen";
5
+ import { addHashAttributesToJsxTagsAst } from "./ast.utils";
6
6
 
7
- describe('AST Utils', () => {
8
- it('Should add the attr to the right elements', async () => {
9
- const compiled = await readFile(resolve(__dirname, './__mocks__/compiled-jsx.js'), { encoding: 'utf-8' })
10
- const compiledWithAttr = await readFile(resolve(__dirname, './__mocks__/compiled-jsx-with-attr.js'), { encoding: 'utf-8' })
11
- const ast = parse(compiled, { ecmaVersion: 2021, sourceType: "module" })
12
- const modifiedAst = addHashAttributesToJsxTagsAst(ast, 'data-v-123456')
13
- const modifiedCompiled = generate(modifiedAst, { format: {
14
- indent: {
15
- style: ' ',
16
- },
17
- quotes: 'double',
18
- } })
7
+ describe("AST Utils", () => {
8
+ beforeEach(() => {
9
+ global.implementation = null;
10
+ });
19
11
 
20
- expect(modifiedCompiled).toEqual(compiledWithAttr)
21
- })
22
- })
12
+ describe("classic implementation", () => {
13
+ it("Should add the attr to the right elements", async () => {
14
+ const compiled = await readFile(
15
+ resolve(__dirname, "./__mocks__/classic-compiled-jsx.js"),
16
+ { encoding: "utf-8" }
17
+ );
18
+ const compiledWithAttr = await readFile(
19
+ resolve(__dirname, "./__mocks__/classic-compiled-jsx-with-attr.js"),
20
+ { encoding: "utf-8" }
21
+ );
22
+ const ast = parse(compiled, { ecmaVersion: 2021, sourceType: "module" });
23
+ const modifiedAst = addHashAttributesToJsxTagsAst(ast, "data-v-123456");
24
+ const modifiedCompiled = generate(modifiedAst, {
25
+ format: {
26
+ indent: {
27
+ style: " ",
28
+ },
29
+ quotes: "double",
30
+ },
31
+ });
32
+
33
+ expect(modifiedCompiled).toEqual(compiledWithAttr);
34
+ });
35
+ });
36
+
37
+ describe("new implementation", () => {
38
+ it("Should add the attr to the right elements", async () => {
39
+ const compiled = await readFile(
40
+ resolve(__dirname, "./__mocks__/new-compiled-jsx.js"),
41
+ { encoding: "utf-8" }
42
+ );
43
+ const compiledWithAttr = await readFile(
44
+ resolve(__dirname, "./__mocks__/new-compiled-jsx-with-attr.js"),
45
+ { encoding: "utf-8" }
46
+ );
47
+ const ast = parse(compiled, { ecmaVersion: 2021, sourceType: "module" });
48
+ const modifiedAst = addHashAttributesToJsxTagsAst(ast, "data-v-123456");
49
+ const modifiedCompiled = generate(modifiedAst, {
50
+ format: {
51
+ indent: {
52
+ style: " ",
53
+ },
54
+ quotes: "double",
55
+ },
56
+ });
57
+
58
+ expect(modifiedCompiled).toEqual(compiledWithAttr);
59
+ });
60
+ });
61
+ });
package/src/ast.utils.ts CHANGED
@@ -1,111 +1,53 @@
1
- /**
2
- * Traverses the AST tree and calls callback for each node.
3
- * -
4
- * @param ast The ast tree
5
- * @param callback Callback function to be called for each node
6
- * @returns
7
- */
8
- function traverse(ast: any, callback: Function) {
9
- for (let key of Object.keys(ast)) {
10
- if (['end', 'start', 'type'].includes(key)) { continue; }
1
+ import { astIterator } from "./ast-iterator";
2
+ import { astTransformer } from "./ast-transformer";
3
+ import { ClassicJsxParser } from "./parsers/classic-parser";
4
+ import { JsxParser, ParserImplementations } from "./parsers/jsx-parser.model";
5
+ import { NewJsxParser } from "./parsers/new-parser";
11
6
 
12
- if (Array.isArray(ast[key])) {
13
- for (let nodeIndex in ast[key]) {
14
- ast[key][nodeIndex] = traverse(ast[key][nodeIndex], callback)
15
- }
16
- } else if(isNode(ast[key])) {
17
- ast[key] = traverse(ast[key], callback)
18
- }
19
- }
20
-
21
- const result = callback(ast);
22
-
23
- return typeof result !== 'undefined' ? result : ast
24
- }
25
-
26
- function isNode(obj: unknown) {
27
- return typeof obj === 'object' &&
28
- typeof (obj as any)?.end === 'number' &&
29
- typeof (obj as any)?.start === 'number' &&
30
- typeof (obj as any)?.type === 'string'
7
+ declare global {
8
+ // eslint-disable-next-line
9
+ var implementation: null | ParserImplementations;
31
10
  }
32
11
 
33
- function isElementWithOldJsxTransform(node: any) {
34
- return node?.type === 'CallExpression' &&
35
- node?.callee?.object?.name === 'React' &&
36
- node?.callee?.property?.name === 'createElement'
37
- }
12
+ global.implementation = null;
38
13
 
39
- function isElementWithNewJsxTransform(node: any) {
40
- return node?.type === 'CallExpression' &&
41
- ['_jsxDEV', '_jsx', '_jsxs'].includes(node?.callee?.name)
42
- }
14
+ const classicParser = new ClassicJsxParser();
15
+ const newParser = new NewJsxParser();
43
16
 
44
- function isFragmentWithOldJsxTransform(node: any) {
45
- return node?.arguments?.[0]?.object?.name === "React" &&
46
- node?.arguments?.[0]?.property?.name === "Fragment"
47
- }
17
+ let parser: JsxParser = classicParser;
48
18
 
49
- function isFragmentWithNewJsxTransform(node: any) {
50
- return node.arguments?.[0]?.type === "Identifier" &&
51
- node.arguments?.[0]?.name === "_Fragment"
52
- }
19
+ const findImplementation = (program: any) => {
20
+ const implementation: ParserImplementations = ParserImplementations.classic;
21
+ for (const node of astIterator(program)) {
22
+ if (node.type !== "ImportDeclaration") {
23
+ continue;
24
+ }
53
25
 
54
- function isFragment(node: any) {
55
- return isFragmentWithOldJsxTransform(node) ||
56
- isFragmentWithNewJsxTransform(node)
57
- }
26
+ if (
27
+ node.specifiers.some((n: any) =>
28
+ ["_jsxDEV", "jsxRuntime"].includes(n.local.name)
29
+ ) ||
30
+ ["react/jsx-dev-runtime", "react/jsx-runtime"].includes(node.source.value)
31
+ ) {
32
+ return ParserImplementations.new;
33
+ }
34
+ }
35
+ return implementation;
36
+ };
58
37
 
59
- function isReactElement(node: any) {
60
- return isElementWithOldJsxTransform(node) ||
61
- isElementWithNewJsxTransform(node)
62
- }
38
+ export function addHashAttributesToJsxTagsAst(program: any, attr: string) {
39
+ // Once in the program, we can determine which parser to use
40
+ if (global.implementation === null) {
41
+ global.implementation = findImplementation(program);
63
42
 
64
- function createAttributeNode(attr: string) {
65
- return {
66
- type: 'Property',
67
- method: false,
68
- shorthand: false,
69
- computed: false,
70
- key: {
71
- type: 'Identifier',
72
- name: `"${attr}"`
73
- },
74
- value: {
75
- type: 'Literal',
76
- value: true,
77
- raw: "true"
78
- },
79
- kind: 'init'
43
+ if (global.implementation === ParserImplementations.new) {
44
+ parser = newParser;
45
+ }
80
46
  }
81
- }
82
47
 
83
- export function addHashAttributesToJsxTagsAst(program: any, attr: string) {
84
- return traverse(program, (node: any) => {
85
- if (isReactElement(node) && !isFragment(node)) {
86
- const arg = node?.arguments?.[1]
87
- return {
88
- ...node,
89
- arguments: node?.arguments.map((v: any, i: number) => {
90
- if (i === 1) {
91
- if (v.type === 'ObjectExpression') {
92
- return {
93
- ...v,
94
- properties: [
95
- ...arg.properties,
96
- createAttributeNode(attr)
97
- ]
98
- }
99
- } else {
100
- return {
101
- type: 'ObjectExpression',
102
- properties: [createAttributeNode(attr)]
103
- }
104
- }
105
- }
106
- return v
107
- })
108
- }
48
+ return astTransformer(program, (node: any) => {
49
+ if (parser.isNodeReactElement(node) && !parser.isNodeReactFragment(node)) {
50
+ return parser.extendNodeWithAttributes(node, attr);
109
51
  }
110
- })
111
- }
52
+ });
53
+ }
package/src/index.ts CHANGED
@@ -1,27 +1,28 @@
1
- import * as path from 'path';
2
- import { xxHash32 } from 'js-xxhash';
3
- import { createFilter, FilterPattern } from '@rollup/pluginutils';
4
- import { compileStyle } from '@vue/component-compiler-utils'
5
- import { generate } from 'escodegen'
1
+ /* eslint-disable no-useless-escape */
2
+ import * as path from "path";
3
+ import { xxHash32 } from "js-xxhash";
4
+ import { createFilter, FilterPattern } from "@rollup/pluginutils";
5
+ import { compileStyle } from "@vue/component-compiler-utils";
6
+ import { generate } from "escodegen";
6
7
 
7
- import type { Plugin } from 'rollup'
8
- import { addHashAttributesToJsxTagsAst } from './ast.utils';
8
+ import type { Plugin } from "rollup";
9
+ import { addHashAttributesToJsxTagsAst } from "./ast.utils";
9
10
 
10
11
  const getFilenameFromPath = (filePath: string) => {
11
- const parts = filePath.split('/')
12
- return parts[parts.length - 1].split('?')[0]
13
- }
12
+ const parts = filePath.split("/");
13
+ return parts[parts.length - 1].split("?")[0];
14
+ };
14
15
 
15
16
  const getHashFromPath = (filePath: string) => {
16
- const search = '?scope='
17
- const hash = filePath.slice(filePath.indexOf(search) + search.length)
18
- return hash
19
- }
17
+ const search = "?scope=";
18
+ const hash = filePath.slice(filePath.indexOf(search) + search.length);
19
+ return hash;
20
+ };
20
21
 
21
- const generateHash = (input: string, seed: number = 0) => {
22
- let hashNum = xxHash32(Buffer.from(input, 'utf8'), seed);
22
+ const generateHash = (input: string, seed = 0) => {
23
+ const hashNum = xxHash32(Buffer.from(input, "utf8"), seed);
23
24
  return hashNum.toString(16);
24
- }
25
+ };
25
26
 
26
27
  const addAttributesToCss = (src: string, fileName: string, hash: string) => {
27
28
  const { code } = compileStyle({
@@ -30,83 +31,93 @@ const addAttributesToCss = (src: string, fileName: string, hash: string) => {
30
31
  id: hash,
31
32
  scoped: true,
32
33
  trim: true,
33
- })
34
+ });
34
35
 
35
- return code
36
- }
36
+ return code;
37
+ };
37
38
 
38
39
  export interface ReactScopedCssPluginOptions {
39
40
  /**
40
41
  * Which files should be included and parsed by the plugin
41
42
  * Default: undefined
42
43
  */
43
- include?: FilterPattern
44
+ include?: FilterPattern;
44
45
 
45
46
  /**
46
47
  * Which files should be exluded and that should not be parsed by the plugin
47
48
  * Default: undefined
48
49
  */
49
- exclude?: FilterPattern
50
+ exclude?: FilterPattern;
50
51
 
51
52
  /**
52
53
  * If you want to customize the stylesheet file pattern
53
54
  * if undefined or '' is passed, all files will be evaluated
54
55
  * Default: 'scoped'
55
56
  */
56
- styleFileSuffix?: string
57
+ styleFileSuffix?: string;
57
58
 
58
59
  /**
59
60
  * If you want to customize the attribute prefix that is added to the jsx elements
60
61
  * Default: 'v'
61
62
  */
62
- hashPrefix?: string
63
+ hashPrefix?: string;
63
64
 
64
65
  /**
65
66
  * If you want to customize the stylesheet extensions
66
67
  * Default: ['scss', 'css', 'sass', 'less']
67
68
  */
68
- styleFileExtensions?: string[]
69
+ styleFileExtensions?: string[];
69
70
 
70
71
  /**
71
72
  * If you have jsx in other file extensions
72
73
  * Default: ['jsx', 'tsx']
73
74
  */
74
- jsxFileExtensions?: string[]
75
+ jsxFileExtensions?: string[];
75
76
  }
76
77
 
77
78
  export interface VitePartialPlugin extends Plugin {
78
- enforce?: "pre" | "post"
79
+ enforce?: "pre" | "post";
79
80
  }
80
81
 
81
- export default function reactScopedCssPlugin(optionsIn: ReactScopedCssPluginOptions = {}): VitePartialPlugin[] {
82
+ export default function reactScopedCssPlugin(
83
+ optionsIn: ReactScopedCssPluginOptions = {}
84
+ ): VitePartialPlugin[] {
82
85
  const options: Partial<ReactScopedCssPluginOptions> = {
83
- hashPrefix: 'v',
84
- styleFileSuffix: 'scoped',
85
- styleFileExtensions: ['scss', 'css', 'sass', 'less'],
86
- jsxFileExtensions: ['jsx', 'tsx'],
87
- ...optionsIn
88
- }
86
+ hashPrefix: "v",
87
+ styleFileSuffix: "scoped",
88
+ styleFileExtensions: ["scss", "css", "sass", "less"],
89
+ jsxFileExtensions: ["jsx", "tsx"],
90
+ ...optionsIn,
91
+ };
89
92
 
90
93
  if (!options.styleFileExtensions || !options.styleFileExtensions.length) {
91
- throw new Error('You need to provide at least one style file extension')
94
+ throw new Error("You need to provide at least one style file extension");
92
95
  }
93
96
 
94
97
  if (!options.jsxFileExtensions || !options.jsxFileExtensions.length) {
95
- throw new Error('You need to provide at least one jsx file extension')
98
+ throw new Error("You need to provide at least one jsx file extension");
96
99
  }
97
100
 
98
- const filter = createFilter( options.include, options.exclude );
99
- const scopedCssRegex = options.styleFileSuffix
100
- ? new RegExp(`\.${options.styleFileSuffix}\.(${options.styleFileExtensions.join('|')})$`)
101
- : new RegExp(`\.(${options.styleFileExtensions.join('|')})$`)
101
+ const filter = createFilter(options.include, options.exclude);
102
+ const scopedCssRegex = options.styleFileSuffix
103
+ ? new RegExp(
104
+ `\.${options.styleFileSuffix}\.(${options.styleFileExtensions.join(
105
+ "|"
106
+ )})$`
107
+ )
108
+ : new RegExp(`\.(${options.styleFileExtensions.join("|")})$`);
102
109
  const scopedCssInFileRegex = options.styleFileSuffix
103
- ? new RegExp(`\.${options.styleFileSuffix}\.(${options.styleFileExtensions.join('|')})(\"|\')`)
104
- : new RegExp(`\.(${options.styleFileExtensions.join('|')})(\"|\')`)
105
- const jsxRegex = new RegExp(`\.(${options.jsxFileExtensions.join('|')})$`)
110
+ ? new RegExp(
111
+ `\.${options.styleFileSuffix}\.(${options.styleFileExtensions.join(
112
+ "|"
113
+ )})(\"|\')`
114
+ )
115
+ : new RegExp(`\.(${options.styleFileExtensions.join("|")})(\"|\')`);
116
+ const jsxRegex = new RegExp(`\.(${options.jsxFileExtensions.join("|")})$`);
106
117
 
107
118
  return [
108
119
  {
109
- name: 'rollup-plugin-react-scoped-css:pre',
120
+ name: "rollup-plugin-react-scoped-css:pre",
110
121
  resolveId(source, importer) {
111
122
  if (!importer) {
112
123
  return;
@@ -114,31 +125,40 @@ export default function reactScopedCssPlugin(optionsIn: ReactScopedCssPluginOpti
114
125
 
115
126
  if (scopedCssRegex.test(source) && jsxRegex.test(importer)) {
116
127
  const importerHash = generateHash(importer);
117
- const url = path.resolve(path.dirname(importer), `${source}?scope=${importerHash}`)
118
- return url
128
+ const url = path.resolve(
129
+ path.dirname(importer),
130
+ `${source}?scope=${importerHash}`
131
+ );
132
+ return url;
119
133
  }
120
134
  },
121
- enforce: 'pre'
135
+ enforce: "pre",
122
136
  },
123
137
  {
124
- name: 'rollup-plugin-react-scoped-css:post',
138
+ name: "rollup-plugin-react-scoped-css:post",
125
139
  transform(code, id) {
126
140
  if (!filter(id)) {
127
141
  return;
128
142
  }
129
-
130
143
  if (scopedCssInFileRegex.test(code)) {
131
144
  const importerHash = generateHash(id);
132
- const program = this.parse(code)
133
- const newAst = addHashAttributesToJsxTagsAst(program, `data-${options.hashPrefix}-${importerHash}`)
134
- return generate(newAst)
145
+ const program = this.parse(code);
146
+ const newAst = addHashAttributesToJsxTagsAst(
147
+ program,
148
+ `data-${options.hashPrefix}-${importerHash}`
149
+ );
150
+ return generate(newAst);
135
151
  }
136
152
 
137
153
  if (scopedCssRegex.test(getFilenameFromPath(id))) {
138
154
  const importerHash = getHashFromPath(id);
139
- return addAttributesToCss(code, getFilenameFromPath(id), `data-${options.hashPrefix}-${importerHash}`)
155
+ return addAttributesToCss(
156
+ code,
157
+ getFilenameFromPath(id),
158
+ `data-${options.hashPrefix}-${importerHash}`
159
+ );
140
160
  }
141
- }
142
- }
143
- ]
144
- }
161
+ },
162
+ },
163
+ ];
164
+ }
@@ -0,0 +1,72 @@
1
+ import { JsxParser } from "./jsx-parser.model";
2
+
3
+ export class ClassicJsxParser implements JsxParser {
4
+ isNodeReactFragment(node: any) {
5
+ return (
6
+ node?.arguments?.[0]?.object?.name === "React" &&
7
+ node?.arguments?.[0]?.property?.name === "Fragment"
8
+ );
9
+ }
10
+ isNodeReactElement(node: any) {
11
+ return (
12
+ node?.type === "CallExpression" &&
13
+ node?.callee?.object?.name === "React" &&
14
+ node?.callee?.property?.name === "createElement"
15
+ );
16
+ }
17
+
18
+ public extendNodeWithAttributes(node: any, attr: string) {
19
+ const arg = node?.arguments?.[1];
20
+ return {
21
+ ...node,
22
+ arguments: node?.arguments.map((v: any, i: number) => {
23
+ if (i === 1) {
24
+ if (v.type === "ObjectExpression") {
25
+ return {
26
+ ...v,
27
+ properties: [...arg.properties, this.createAttributeNode(attr)],
28
+ };
29
+ } else if (
30
+ v.type === "CallExpression" &&
31
+ v.callee.name === "_objectSpread"
32
+ ) {
33
+ const args = [...v.arguments];
34
+ args[0].properties = [
35
+ ...args[0].properties,
36
+ this.createAttributeNode(attr),
37
+ ];
38
+ return {
39
+ ...v,
40
+ arguments: args,
41
+ };
42
+ } else {
43
+ return {
44
+ type: "ObjectExpression",
45
+ properties: [this.createAttributeNode(attr)],
46
+ };
47
+ }
48
+ }
49
+ return v;
50
+ }),
51
+ };
52
+ }
53
+
54
+ private createAttributeNode(attr: string) {
55
+ return {
56
+ type: "Property",
57
+ method: false,
58
+ shorthand: false,
59
+ computed: false,
60
+ key: {
61
+ type: "Identifier",
62
+ name: `"${attr}"`,
63
+ },
64
+ value: {
65
+ type: "Literal",
66
+ value: true,
67
+ raw: "true",
68
+ },
69
+ kind: "init",
70
+ };
71
+ }
72
+ }
@@ -0,0 +1,10 @@
1
+ export interface JsxParser {
2
+ isNodeReactElement: (node: any) => boolean;
3
+ isNodeReactFragment: (node: any) => boolean;
4
+ extendNodeWithAttributes: (node: any, attr: string) => any;
5
+ }
6
+
7
+ export enum ParserImplementations {
8
+ new = "new",
9
+ classic = "classic",
10
+ }