Skip to content

Commit e8efc93

Browse files
Bart LedouxBart Ledoux
authored andcommitted
fix: add some tests and make remark work
1 parent 659656f commit e8efc93

File tree

6 files changed

+4251
-30
lines changed

6 files changed

+4251
-30
lines changed

getAst.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const { Parser } = require("acorn");
2+
3+
const ACORN_OPTIONS = {
4+
ecmaVersion: 2019,
5+
sourceType: "module",
6+
};
7+
8+
/**
9+
* Parse source code with Acorn and return AST, returns undefined in case of errors
10+
*/
11+
module.exports = function getAst(code, plugins = []) {
12+
const parser = Parser.extend(...plugins);
13+
14+
try {
15+
return parser.parse(code, ACORN_OPTIONS);
16+
} catch (err) {
17+
return undefined;
18+
}
19+
};

getImports.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const walkes = require("walkes");
2+
const getAst = require("./getAst");
3+
4+
/**
5+
* Returns a list of all strings used in import statements or require() calls
6+
*/
7+
module.exports = function getImports(code) {
8+
// Parse example source code, but ignore errors
9+
const ast = getAst(code);
10+
if (!ast) {
11+
return [];
12+
}
13+
14+
const imports = [];
15+
walkes(ast, {
16+
// import foo from '
17+
// import 'foo'
18+
ImportDeclaration(node) {
19+
if (node.source) {
20+
imports.push(node.source.value);
21+
}
22+
},
23+
// require('foo')
24+
CallExpression(node) {
25+
if (
26+
node.callee &&
27+
node.callee.name === "require" &&
28+
node.arguments &&
29+
node.arguments[0].value
30+
) {
31+
imports.push(node.arguments[0].value);
32+
}
33+
},
34+
});
35+
return imports;
36+
};

index.js

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
const visit = require("unist-util-visit");
22
const { parseComponent } = require("vue-template-compiler");
33
const { isCodeVueSfc } = require("vue-inbrowser-compiler-utils");
4+
const getImports = require("./getImports");
45

5-
export default function attacher({ liveFilter } = {}) {
6-
return (ast) => visit(ast, "code", visitor);
7-
6+
function getAttacher({ liveFilter } = {}) {
87
function visitor(node) {
9-
let { lang } = node;
8+
let { lang, meta } = node;
109

1110
if (
1211
liveFilter
13-
? !liveFilter(lang)
14-
: !/ live$/.test(lang) && !/ live /.test(lang)
12+
? !liveFilter(lang, meta)
13+
: !/live$/.test(meta) && !/live /.test(meta)
1514
) {
1615
return;
1716
}
@@ -35,17 +34,40 @@ export default function attacher({ liveFilter } = {}) {
3534
// add this as a prop
3635
const scr = getScript(code);
3736

38-
const langArray = lang.split(" ");
39-
const langClean = langArray[0];
37+
const requires = getImports(scr).map(
38+
(mod) => `'${mod}': require('${mod}')`
39+
);
40+
4041
const codeClean = code.replace(/\`/g, "\\`").replace(/\$/g, "\\$");
41-
const editorProps = langArray.find((l) => /^\{.+\}$/.test(l));
42-
const jsx = langArray.length > 2 && langArray[1] === "jsx" ? "jsx " : ""; // to enable jsx, we want ```vue jsx live or ```jsx jsx live
42+
43+
const editorPropsArray = /\{.+\}/.exec(meta);
44+
const editorProps = editorPropsArray ? editorPropsArray[0] : undefined;
45+
const metaArray = meta ? meta.replace(editorProps, "").split(" ") : [];
46+
const jsx = metaArray.length > 2 && metaArray[1] === "jsx" ? "jsx " : ""; // to enable jsx, we want ```vue jsx live or ```jsx jsx live
4347
const markdownGenerated = `<vue-live ${jsx}
44-
:layoutProps="{lang:'${langClean}'}"
45-
:code="\`${codeClean}\`"
46-
${editorProps ? ` :editorProps="${editorProps}"` : ""}
47-
/>`;
48+
:layoutProps="{lang:'${lang}'}"${
49+
requires.length
50+
? `
51+
:requires="{${requires.join(",")}}"`
52+
: ""
53+
}
54+
:code="\`${codeClean}\`" ${
55+
editorProps
56+
? `
57+
:editorProps="${editorProps
58+
.replace(/"/g, "&quot;")
59+
.replace(/</g, "&lt;")
60+
.replace(/>/g, "&gt;")}"`
61+
: ""
62+
} />`;
4863

64+
node.type = "html";
4965
node.value = markdownGenerated;
5066
}
67+
68+
return function attacher() {
69+
return (ast) => visit(ast, "code", visitor);
70+
};
5171
}
72+
73+
module.exports = getAttacher;

index.spec.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
const remark = require("remark");
2+
const plugin = require("./index.js");
3+
4+
test("transform fenced into vue-live with live flag", () => {
5+
remark()
6+
.use(plugin())
7+
.process(
8+
`
9+
\`\`\`vue live
10+
<comp/>
11+
\`\`\`
12+
`,
13+
(err, file) => {
14+
if (err) {
15+
throw err;
16+
}
17+
expect(String(file)).toMatchInlineSnapshot(`
18+
"<vue-live
19+
:layoutProps=\\"{lang:'vue'}\\"
20+
:code=\\"\`<comp/>\`\\" />
21+
"
22+
`);
23+
}
24+
);
25+
});
26+
27+
test("avoid transform fenced into vue-live", () => {
28+
remark()
29+
.use(plugin())
30+
.process(
31+
`
32+
\`\`\`vue
33+
<comp/>
34+
\`\`\`
35+
`,
36+
(err, file) => {
37+
if (err) {
38+
throw err;
39+
}
40+
expect(String(file)).toMatchInlineSnapshot(`
41+
"\`\`\`vue
42+
<comp/>
43+
\`\`\`
44+
"
45+
`);
46+
}
47+
);
48+
});
49+
50+
test("transform custom fenced into vue-live", () => {
51+
remark()
52+
.use(plugin({ liveFilter: (lang) => /pizza/.test(lang) }))
53+
.process(
54+
`
55+
\`\`\`pizza
56+
<comp/>
57+
\`\`\`
58+
`,
59+
(err, file) => {
60+
if (err) {
61+
throw err;
62+
}
63+
expect(String(file)).toMatchInlineSnapshot(`
64+
"<vue-live
65+
:layoutProps=\\"{lang:'pizza'}\\"
66+
:code=\\"\`<comp/>\`\\" />
67+
"
68+
`);
69+
}
70+
);
71+
});
72+
73+
test("transform require and import statements", () => {
74+
remark()
75+
.use(plugin())
76+
.process(
77+
`
78+
\`\`\`js live
79+
import b from 'test/import'
80+
const a = require('test/req')
81+
<comp/>
82+
\`\`\`
83+
`,
84+
(err, file) => {
85+
if (err) {
86+
throw err;
87+
}
88+
expect(String(file)).toMatchInlineSnapshot(`
89+
"<vue-live
90+
:layoutProps=\\"{lang:'js'}\\"
91+
:requires=\\"{'test/import': require('test/import'),'test/req': require('test/req')}\\"
92+
:code=\\"\`import b from 'test/import'
93+
const a = require('test/req')
94+
<comp/>\`\\" />
95+
"
96+
`);
97+
}
98+
);
99+
});
100+
101+
test("transform require and import statements", () => {
102+
remark()
103+
.use(plugin())
104+
.process(
105+
`
106+
\`\`\`js live {"lineNumbers": true}
107+
<comp/>
108+
\`\`\`
109+
`,
110+
(err, file) => {
111+
if (err) {
112+
throw err;
113+
}
114+
expect(String(file)).toMatchInlineSnapshot(`
115+
"<vue-live
116+
:layoutProps=\\"{lang:'js'}\\"
117+
:code=\\"\`<comp/>\`\\"
118+
:editorProps=\\"{&quot;lineNumbers&quot;: true}\\" />
119+
"
120+
`);
121+
}
122+
);
123+
});

package.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "remark-plugin-vue-live",
3-
"version": "0.0.1",
3+
"version": "0.0.0-dev",
44
"description": "Remark plugin to transform fenced code into examples in vue-live",
55
"main": "index.js",
66
"repository": {
@@ -20,11 +20,21 @@
2020
},
2121
"homepage": "https://github.com/vue-styleguidist/remark-plugin-vue-live#readme",
2222
"dependencies": {
23+
"acorn": "^6.4.2",
2324
"unist-util-visit": "^2.0.3",
2425
"vue-inbrowser-compiler-utils": "^4.32.1",
25-
"vue-template-compiler": "^2.6.12"
26+
"vue-template-compiler": "^2.6.12",
27+
"walkes": "^0.2.1"
2628
},
2729
"peerDependencies": {
2830
"vue-live": "^1.14.0"
31+
},
32+
"devDependencies": {
33+
"jest": "^26.5.2",
34+
"prettier": "^2.1.2",
35+
"remark": "^12.0.1"
36+
},
37+
"scripts": {
38+
"test": "jest"
2939
}
3040
}

0 commit comments

Comments
 (0)