diff --git a/netlify.toml b/netlify.toml index 8380f27d0..adc4dff4c 100644 --- a/netlify.toml +++ b/netlify.toml @@ -5,8 +5,3 @@ [build.environment] NODE_VERSION = "lts/*" YARN_VERSION = "1.22.22" - -[[headers]] - for = "/worker.js" - [headers.values] - Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-eval'" diff --git a/website/playground/Playground.jsx b/website/playground/Playground.jsx index 0357fd837..42c2b5e93 100644 --- a/website/playground/Playground.jsx +++ b/website/playground/Playground.jsx @@ -97,10 +97,9 @@ class Playground extends React.Component { const codeSample = getCodeSample(options.parser); const content = original.content || codeSample; const isDocExplorer = options.parser === "doc-explorer"; - const needsClickForFirstRun = isDocExplorer && content !== codeSample; const selection = {}; - this.state = { content, options, selection, needsClickForFirstRun }; + this.state = { content, options, selection }; this.handleOptionValueChange = this.handleOptionValueChange.bind(this); @@ -171,10 +170,6 @@ class Playground extends React.Component { ? getCodeSample(options.parser) : state.content; - if (option.name === "parser") { - state.needsClickForFirstRun = false; - } - return { options, content }; }); } @@ -251,7 +246,6 @@ class Playground extends React.Component { {(editorState) => ( ) : null} {editorState.showOutput ? ( - this.state.needsClickForFirstRun ? ( -
-
- doc-explorer involves running code - provided by users. -
-
- To stay on the safe side and prevent abuse, an - explicit user action is required when a direct - link to a doc-explorer playground is - opened. -
-
- Click the button below to start the playground. -
- -
- ) : ( - - ) + ) : null} {editorState.showSecondFormat && !isDocExplorer ? ( + plugin.parsers?.includes(expressionParserName), + ); + const pluginModule = await import(`./lib/${plugin.file}`); + return pluginModule.parsers[expressionParserName]; +} + +// `Symbol.for('description')` +function isSymbol(node) { + return ( + node.type === "CallExpression" && + node.callee.type === "MemberExpression" && + !node.callee.computed && + !node.callee.optional && + node.callee.object.type === "Identifier" && + node.callee.object.name === "Symbol" && + node.callee.property.type === "Identifier" && + node.callee.property.name === "for" && + node.arguments.length === 1 && + node.arguments[0].type === "StringLiteral" + ); +} + +function validateNode(node) { + if (!node?.type) { + return; + } + + if (node.type === "ObjectExpression") { + for (const property of node.properties) { + if ( + property.type !== "ObjectProperty" || + property.computed || + property.shorthand || + property.method + ) { + throw new Error("Unexpected object property."); + } + + const { value } = property; + + if (isSymbol(value)) { + continue; + } + + validateNode(value); + } + return; + } + + if (node.type === "Identifier" && !builderNames.has(node.name)) { + throw new Error(`Unexpected identifier name '${node.name}'.`); + } + + for (const value of Object.values(node)) { + const children = Array.isArray(value) ? value : [value]; + + for (const child of children) { + validateNode(child); + } + } +} + +let expressionParser; +async function validateDoc(text) { + expressionParser ??= await getJsExpressionParser(); + + const ast = expressionParser.parse(text); + if (ast.type !== "JsExpressionRoot") { + throw new Error("Invalid doc"); + } + + validateNode(ast.node); +} + +async function parse(text) { + await validateDoc(text); + + const value = new Function( + `{ ${Object.keys(prettier.doc.builders)} }`, + `const result = (${text || "''"}\n); return result;`, + )(prettier.doc.builders); + + return { value }; +} + +export const parsers = { + [parserName]: { + parse, + astFormat, + }, +}; +export const printers = { + [astFormat]: { + print: ({ node }) => node.value, + }, +}; + +export const languages = [{ name: languageName, parsers: [parserName] }]; diff --git a/website/static/worker.mjs b/website/static/worker.mjs index 6f318ae34..c5c1a1c87 100644 --- a/website/static/worker.mjs +++ b/website/static/worker.mjs @@ -1,5 +1,6 @@ import prettierPackageManifest from "./lib/package-manifest.mjs"; import * as prettier from "./lib/prettier/standalone.mjs"; +import * as prettierPluginDocExplorer from "./prettier-plugin-doc-explorer.mjs"; const pluginLoadPromises = new Map(); async function importPlugin(plugin) { @@ -38,28 +39,9 @@ function createPlugin(pluginManifest) { return plugin; } -const docExplorerPlugin = { - parsers: { - "doc-explorer": { - parse: (text) => - new Function( - `{ ${Object.keys(prettier.doc.builders)} }`, - `const result = (${text || "''"}\n); return result;`, - )(prettier.doc.builders), - astFormat: "doc-explorer", - }, - }, - printers: { - "doc-explorer": { - print: (path) => path.getValue(), - }, - }, - languages: [{ name: "doc-explorer", parsers: ["doc-explorer"] }], -}; - const plugins = [ ...prettierPackageManifest.plugins.map((plugin) => createPlugin(plugin)), - docExplorerPlugin, + prettierPluginDocExplorer, ]; self.onmessage = async function (event) {