✨ Added php prettier plugin
This commit is contained in:
1814
frontend/package-lock.json
generated
1814
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -49,7 +49,11 @@
|
||||
"colors-named-hex": "^1.0.2",
|
||||
"franc-min": "^6.2.0",
|
||||
"hsl-matcher": "^1.2.4",
|
||||
"jsox": "^1.2.123",
|
||||
"lezer": "^0.13.5",
|
||||
"linguist-languages": "^9.0.0",
|
||||
"node-sql-parser": "^5.3.12",
|
||||
"php-parser": "^3.2.5",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.5.0",
|
||||
"prettier": "^3.6.2",
|
||||
@@ -75,6 +79,7 @@
|
||||
"typescript-eslint": "^8.43.0",
|
||||
"unplugin-vue-components": "^29.0.0",
|
||||
"vite": "^7.1.5",
|
||||
"vite-plugin-node-polyfills": "^0.24.0",
|
||||
"vue-eslint-parser": "^10.2.0",
|
||||
"vue-tsc": "^3.0.6"
|
||||
}
|
||||
|
||||
4
frontend/src/utils/prettier/plugins/php/index.d.ts
vendored
Normal file
4
frontend/src/utils/prettier/plugins/php/index.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import type { Plugin } from "prettier";
|
||||
|
||||
declare const plugin: Plugin;
|
||||
export default plugin;
|
||||
19
frontend/src/utils/prettier/plugins/php/index.mjs
Normal file
19
frontend/src/utils/prettier/plugins/php/index.mjs
Normal file
@@ -0,0 +1,19 @@
|
||||
export {
|
||||
languages,
|
||||
printers,
|
||||
parsers,
|
||||
options,
|
||||
defaultOptions
|
||||
} from './src/index.mjs';
|
||||
|
||||
import { languages, printers, parsers, options, defaultOptions } from './src/index.mjs';
|
||||
|
||||
const phpPlugin = {
|
||||
languages,
|
||||
printers,
|
||||
parsers,
|
||||
options,
|
||||
defaultOptions
|
||||
};
|
||||
|
||||
export default phpPlugin;
|
||||
111
frontend/src/utils/prettier/plugins/php/src/clean.mjs
Normal file
111
frontend/src/utils/prettier/plugins/php/src/clean.mjs
Normal file
@@ -0,0 +1,111 @@
|
||||
import { printNumber, normalizeMagicMethodName } from "./util.mjs";
|
||||
|
||||
const ignoredProperties = new Set([
|
||||
"loc",
|
||||
"range",
|
||||
"raw",
|
||||
"comments",
|
||||
"leadingComments",
|
||||
"trailingComments",
|
||||
"parenthesizedExpression",
|
||||
"parent",
|
||||
"prev",
|
||||
"start",
|
||||
"end",
|
||||
"tokens",
|
||||
"errors",
|
||||
"extra",
|
||||
]);
|
||||
|
||||
/**
|
||||
* This function takes the existing ast node and a copy, by reference
|
||||
* We use it for testing, so that we can compare pre-post versions of the AST,
|
||||
* excluding things we don't care about (like node location, case that will be
|
||||
* changed by the printer, etc.)
|
||||
*/
|
||||
function clean(node, newObj) {
|
||||
if (node.kind === "string") {
|
||||
// TODO if options are available in this method, replace with
|
||||
// newObj.isDoubleQuote = !useSingleQuote(node, options);
|
||||
delete newObj.isDoubleQuote;
|
||||
}
|
||||
|
||||
if (["array", "list"].includes(node.kind)) {
|
||||
// TODO if options are available in this method, assign instead of delete
|
||||
delete newObj.shortForm;
|
||||
}
|
||||
|
||||
if (node.kind === "inline") {
|
||||
if (node.value.includes("___PSEUDO_INLINE_PLACEHOLDER___")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
newObj.value = newObj.value.replace(/\n/g, "");
|
||||
}
|
||||
|
||||
// continue ((2)); -> continue 2;
|
||||
// continue 1; -> continue;
|
||||
if ((node.kind === "continue" || node.kind === "break") && node.level) {
|
||||
const { level } = newObj;
|
||||
|
||||
if (level.kind === "number") {
|
||||
newObj.level = level.value === "1" ? null : level;
|
||||
}
|
||||
}
|
||||
|
||||
// if () {{ }} -> if () {}
|
||||
if (node.kind === "block") {
|
||||
if (node.children.length === 1 && node.children[0].kind === "block") {
|
||||
while (newObj.children[0].kind === "block") {
|
||||
newObj.children = newObj.children[0].children;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize numbers
|
||||
if (node.kind === "number") {
|
||||
newObj.value = printNumber(node.value);
|
||||
}
|
||||
|
||||
const statements = ["foreach", "for", "if", "while", "do"];
|
||||
|
||||
if (statements.includes(node.kind)) {
|
||||
if (node.body && node.body.kind !== "block") {
|
||||
newObj.body = {
|
||||
kind: "block",
|
||||
children: [newObj.body],
|
||||
};
|
||||
} else {
|
||||
newObj.body = newObj.body ? newObj.body : null;
|
||||
}
|
||||
|
||||
if (node.alternate && node.alternate.kind !== "block") {
|
||||
newObj.alternate = {
|
||||
kind: "block",
|
||||
children: [newObj.alternate],
|
||||
};
|
||||
} else {
|
||||
newObj.alternate = newObj.alternate ? newObj.alternate : null;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.kind === "usegroup" && typeof node.name === "string") {
|
||||
newObj.name = newObj.name.replace(/^\\/, "");
|
||||
}
|
||||
|
||||
if (node.kind === "useitem") {
|
||||
newObj.name = newObj.name.replace(/^\\/, "");
|
||||
}
|
||||
|
||||
if (node.kind === "method" && node.name.kind === "identifier") {
|
||||
newObj.name.name = normalizeMagicMethodName(newObj.name.name);
|
||||
}
|
||||
|
||||
if (node.kind === "noop") {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
clean.ignoredProperties = ignoredProperties;
|
||||
|
||||
export default clean;
|
||||
1044
frontend/src/utils/prettier/plugins/php/src/comments.mjs
Normal file
1044
frontend/src/utils/prettier/plugins/php/src/comments.mjs
Normal file
File diff suppressed because it is too large
Load Diff
173
frontend/src/utils/prettier/plugins/php/src/index.mjs
Normal file
173
frontend/src/utils/prettier/plugins/php/src/index.mjs
Normal file
@@ -0,0 +1,173 @@
|
||||
import { doc } from "prettier";
|
||||
import LINGUIST_LANGUAGES_PHP from "linguist-languages/data/PHP";
|
||||
import LINGUIST_LANGUAGES_HTML_PHP from "linguist-languages/data/HTML_2b_PHP";
|
||||
import parse from "./parser.mjs";
|
||||
import print from "./printer.mjs";
|
||||
import clean from "./clean.mjs";
|
||||
import options from "./options.mjs";
|
||||
import {
|
||||
handleOwnLineComment,
|
||||
handleEndOfLineComment,
|
||||
handleRemainingComment,
|
||||
getCommentChildNodes,
|
||||
canAttachComment,
|
||||
isBlockComment,
|
||||
} from "./comments.mjs";
|
||||
import { hasPragma, insertPragma } from "./pragma.mjs";
|
||||
import { locStart, locEnd } from "./loc.mjs";
|
||||
|
||||
const { join, hardline } = doc.builders;
|
||||
|
||||
function createLanguage(linguistData, { extend, override }) {
|
||||
const language = {};
|
||||
|
||||
for (const key in linguistData) {
|
||||
const newKey = key === "languageId" ? "linguistLanguageId" : key;
|
||||
language[newKey] = linguistData[key];
|
||||
}
|
||||
|
||||
if (extend) {
|
||||
for (const key in extend) {
|
||||
language[key] = (language[key] || []).concat(extend[key]);
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in override) {
|
||||
language[key] = override[key];
|
||||
}
|
||||
|
||||
return language;
|
||||
}
|
||||
|
||||
const languages = [
|
||||
createLanguage(LINGUIST_LANGUAGES_PHP, {
|
||||
override: {
|
||||
parsers: ["php"],
|
||||
vscodeLanguageIds: ["php"],
|
||||
},
|
||||
}),
|
||||
createLanguage(LINGUIST_LANGUAGES_HTML_PHP, {
|
||||
override: {
|
||||
parsers: ["php"],
|
||||
vscodeLanguageIds: ["php"],
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
const parsers = {
|
||||
php: {
|
||||
parse,
|
||||
astFormat: "php",
|
||||
locStart,
|
||||
locEnd,
|
||||
hasPragma,
|
||||
},
|
||||
};
|
||||
|
||||
const ignoredKeys = new Set([
|
||||
"kind",
|
||||
"loc",
|
||||
"errors",
|
||||
"extra",
|
||||
"comments",
|
||||
"leadingComments",
|
||||
"enclosingNode",
|
||||
"precedingNode",
|
||||
"followingNode",
|
||||
]);
|
||||
function getVisitorKeys(node, nonTraversableKeys) {
|
||||
return Object.keys(node).filter(
|
||||
(key) => !nonTraversableKeys.has(key) && !ignoredKeys.has(key)
|
||||
);
|
||||
}
|
||||
|
||||
const printers = {
|
||||
php: {
|
||||
print,
|
||||
getVisitorKeys,
|
||||
insertPragma,
|
||||
massageAstNode: clean,
|
||||
getCommentChildNodes,
|
||||
canAttachComment,
|
||||
isBlockComment,
|
||||
handleComments: {
|
||||
ownLine: handleOwnLineComment,
|
||||
endOfLine: handleEndOfLineComment,
|
||||
remaining: handleRemainingComment,
|
||||
},
|
||||
willPrintOwnComments(path) {
|
||||
const { node } = path;
|
||||
|
||||
return node && node.kind === "noop";
|
||||
},
|
||||
printComment(path) {
|
||||
const comment = path.node;
|
||||
|
||||
switch (comment.kind) {
|
||||
case "commentblock": {
|
||||
// for now, don't touch single line block comments
|
||||
if (!comment.value.includes("\n")) {
|
||||
return comment.value;
|
||||
}
|
||||
|
||||
const lines = comment.value.split("\n");
|
||||
// if this is a block comment, handle indentation
|
||||
if (
|
||||
lines
|
||||
.slice(1, lines.length - 1)
|
||||
.every((line) => line.trim()[0] === "*")
|
||||
) {
|
||||
return join(
|
||||
hardline,
|
||||
lines.map(
|
||||
(line, index) =>
|
||||
(index > 0 ? " " : "") +
|
||||
(index < lines.length - 1 ? line.trim() : line.trimLeft())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// otherwise we can't be sure about indentation, so just print as is
|
||||
return comment.value;
|
||||
}
|
||||
case "commentline": {
|
||||
return comment.value.trimRight();
|
||||
}
|
||||
/* c8 ignore next 2 */
|
||||
default:
|
||||
throw new Error(`Not a comment: ${JSON.stringify(comment)}`);
|
||||
}
|
||||
},
|
||||
hasPrettierIgnore(path) {
|
||||
const isSimpleIgnore = (comment) =>
|
||||
comment.value.includes("prettier-ignore") &&
|
||||
!comment.value.includes("prettier-ignore-start") &&
|
||||
!comment.value.includes("prettier-ignore-end");
|
||||
|
||||
const { node, parent: parentNode } = path;
|
||||
|
||||
return (
|
||||
(node &&
|
||||
node.kind !== "classconstant" &&
|
||||
node.comments &&
|
||||
node.comments.length > 0 &&
|
||||
node.comments.some(isSimpleIgnore)) ||
|
||||
// For proper formatting, the classconstant ignore formatting should
|
||||
// run on the "constant" child
|
||||
(node &&
|
||||
node.kind === "constant" &&
|
||||
parentNode &&
|
||||
parentNode.kind === "classconstant" &&
|
||||
parentNode.comments &&
|
||||
parentNode.comments.length > 0 &&
|
||||
parentNode.comments.some(isSimpleIgnore))
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const defaultOptions = {
|
||||
tabWidth: 4,
|
||||
};
|
||||
|
||||
export { languages, printers, parsers, options, defaultOptions };
|
||||
4
frontend/src/utils/prettier/plugins/php/src/loc.mjs
Normal file
4
frontend/src/utils/prettier/plugins/php/src/loc.mjs
Normal file
@@ -0,0 +1,4 @@
|
||||
const loc = (prop) => (node) => node.loc?.[prop]?.offset;
|
||||
|
||||
export const locStart = loc("start");
|
||||
export const locEnd = loc("end");
|
||||
250
frontend/src/utils/prettier/plugins/php/src/needs-parens.mjs
Normal file
250
frontend/src/utils/prettier/plugins/php/src/needs-parens.mjs
Normal file
@@ -0,0 +1,250 @@
|
||||
import { getPrecedence, shouldFlatten, isBitwiseOperator } from "./util.mjs";
|
||||
|
||||
function needsParens(path, options) {
|
||||
const { parent } = path;
|
||||
|
||||
if (!parent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { key, node } = path;
|
||||
|
||||
if (
|
||||
[
|
||||
// No need parens for top level children of this nodes
|
||||
"program",
|
||||
"expressionstatement",
|
||||
"namespace",
|
||||
"declare",
|
||||
"block",
|
||||
|
||||
// No need parens
|
||||
"include",
|
||||
"print",
|
||||
"return",
|
||||
"echo",
|
||||
].includes(parent.kind)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (node.kind) {
|
||||
case "pre":
|
||||
case "post":
|
||||
if (parent.kind === "unary") {
|
||||
return (
|
||||
node.kind === "pre" &&
|
||||
((node.type === "+" && parent.type === "+") ||
|
||||
(node.type === "-" && parent.type === "-"))
|
||||
);
|
||||
}
|
||||
// else fallthrough
|
||||
case "unary":
|
||||
switch (parent.kind) {
|
||||
case "unary":
|
||||
return (
|
||||
node.type === parent.type &&
|
||||
(node.type === "+" || node.type === "-")
|
||||
);
|
||||
case "propertylookup":
|
||||
case "nullsafepropertylookup":
|
||||
case "staticlookup":
|
||||
case "offsetlookup":
|
||||
case "call":
|
||||
return key === "what";
|
||||
case "bin":
|
||||
return parent.type === "**" && key === "left";
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
case "bin": {
|
||||
switch (parent.kind) {
|
||||
case "assign":
|
||||
case "retif":
|
||||
return ["and", "xor", "or"].includes(node.type);
|
||||
case "silent":
|
||||
case "cast":
|
||||
// TODO: bug https://github.com/glayzzle/php-parser/issues/172
|
||||
return node.parenthesizedExpression;
|
||||
case "pre":
|
||||
case "post":
|
||||
case "unary":
|
||||
return true;
|
||||
case "call":
|
||||
case "propertylookup":
|
||||
case "nullsafepropertylookup":
|
||||
case "staticlookup":
|
||||
case "offsetlookup":
|
||||
return key === "what";
|
||||
case "bin": {
|
||||
const po = parent.type;
|
||||
const pp = getPrecedence(po);
|
||||
const no = node.type;
|
||||
const np = getPrecedence(no);
|
||||
|
||||
if (pp > np) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (po === "||" && no === "&&") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pp === np && key === "right") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pp === np && !shouldFlatten(po, no)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pp < np && no === "%") {
|
||||
return po === "+" || po === "-";
|
||||
}
|
||||
|
||||
// Add parenthesis when working with bitwise operators
|
||||
// It's not stricly needed but helps with code understanding
|
||||
if (isBitwiseOperator(po)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case "propertylookup":
|
||||
case "nullsafepropertylookup":
|
||||
case "staticlookup": {
|
||||
switch (parent.kind) {
|
||||
case "call":
|
||||
return key === "what" && node.parenthesizedExpression;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case "clone":
|
||||
case "new": {
|
||||
const requiresParens =
|
||||
node.kind === "clone" ||
|
||||
(node.kind === "new" && options.phpVersion < 8.4);
|
||||
switch (parent.kind) {
|
||||
case "propertylookup":
|
||||
case "nullsafepropertylookup":
|
||||
case "staticlookup":
|
||||
case "offsetlookup":
|
||||
case "call":
|
||||
return key === "what" && requiresParens;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case "yield": {
|
||||
switch (parent.kind) {
|
||||
case "propertylookup":
|
||||
case "nullsafepropertylookup":
|
||||
case "staticlookup":
|
||||
case "offsetlookup":
|
||||
case "call":
|
||||
return key === "what";
|
||||
|
||||
case "retif":
|
||||
return key === "test";
|
||||
|
||||
default:
|
||||
return !!(node.key || node.value);
|
||||
}
|
||||
}
|
||||
case "assign": {
|
||||
if (
|
||||
parent.kind === "for" &&
|
||||
(parent.init.includes(node) || parent.increment.includes(node))
|
||||
) {
|
||||
return false;
|
||||
} else if (parent.kind === "assign") {
|
||||
return false;
|
||||
} else if (parent.kind === "static") {
|
||||
return false;
|
||||
} else if (
|
||||
["if", "do", "while", "foreach", "switch"].includes(parent.kind)
|
||||
) {
|
||||
return false;
|
||||
} else if (parent.kind === "silent") {
|
||||
return false;
|
||||
} else if (parent.kind === "call") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
case "retif":
|
||||
switch (parent.kind) {
|
||||
case "cast":
|
||||
return true;
|
||||
case "unary":
|
||||
case "bin":
|
||||
case "retif":
|
||||
if (key === "test" && !parent.trueExpr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
case "propertylookup":
|
||||
case "nullsafepropertylookup":
|
||||
case "staticlookup":
|
||||
case "offsetlookup":
|
||||
case "call":
|
||||
return key === "what";
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
case "closure":
|
||||
switch (parent.kind) {
|
||||
case "call":
|
||||
return key === "what";
|
||||
|
||||
// https://github.com/prettier/plugin-php/issues/1675
|
||||
case "propertylookup":
|
||||
case "nullsafepropertylookup":
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
case "silence":
|
||||
case "cast":
|
||||
// TODO: bug https://github.com/glayzzle/php-parser/issues/172
|
||||
return node.parenthesizedExpression;
|
||||
// else fallthrough
|
||||
case "string":
|
||||
case "array":
|
||||
switch (parent.kind) {
|
||||
case "propertylookup":
|
||||
case "nullsafepropertylookup":
|
||||
case "staticlookup":
|
||||
case "offsetlookup":
|
||||
case "call":
|
||||
if (
|
||||
["string", "array"].includes(node.kind) &&
|
||||
parent.kind === "offsetlookup"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return key === "what";
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
case "print":
|
||||
case "include":
|
||||
return parent.kind === "bin";
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export default needsParens;
|
||||
69
frontend/src/utils/prettier/plugins/php/src/options.mjs
Normal file
69
frontend/src/utils/prettier/plugins/php/src/options.mjs
Normal file
@@ -0,0 +1,69 @@
|
||||
const CATEGORY_PHP = "PHP";
|
||||
|
||||
// prettier-ignore
|
||||
const SUPPORTED_PHP_VERSIONS = [
|
||||
5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6,
|
||||
7.0, 7.1, 7.2, 7.3, 7.4,
|
||||
8.0, 8.1, 8.2, 8.3, 8.4,
|
||||
];
|
||||
|
||||
export const LATEST_SUPPORTED_PHP_VERSION = Math.max(...SUPPORTED_PHP_VERSIONS);
|
||||
|
||||
/**
|
||||
* Resolve the PHP version to a number based on the provided options.
|
||||
*/
|
||||
export function resolvePhpVersion(options) {
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.phpVersion === "auto" || options.phpVersion === "composer") {
|
||||
options.phpVersion = LATEST_SUPPORTED_PHP_VERSION;
|
||||
} else {
|
||||
options.phpVersion = parseFloat(options.phpVersion);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
phpVersion: {
|
||||
since: "0.13.0",
|
||||
category: CATEGORY_PHP,
|
||||
type: "choice",
|
||||
default: "auto",
|
||||
description: "Minimum target PHP version.",
|
||||
choices: [
|
||||
...SUPPORTED_PHP_VERSIONS.map((v) => ({ value: v.toFixed(1) })),
|
||||
{
|
||||
value: "auto",
|
||||
description: `Use latest PHP Version (${LATEST_SUPPORTED_PHP_VERSION})`,
|
||||
},
|
||||
],
|
||||
},
|
||||
trailingCommaPHP: {
|
||||
since: "0.0.0",
|
||||
category: CATEGORY_PHP,
|
||||
type: "boolean",
|
||||
default: true,
|
||||
description: "Print trailing commas wherever possible when multi-line.",
|
||||
},
|
||||
braceStyle: {
|
||||
since: "0.10.0",
|
||||
category: CATEGORY_PHP,
|
||||
type: "choice",
|
||||
default: "per-cs",
|
||||
description:
|
||||
"Print one space or newline for code blocks (classes and functions).",
|
||||
choices: [
|
||||
{ value: "psr-2", description: "(deprecated) Use per-cs" },
|
||||
{ value: "per-cs", description: "Use the PER Coding Style brace style." },
|
||||
{ value: "1tbs", description: "Use 1tbs brace style." },
|
||||
],
|
||||
},
|
||||
singleQuote: {
|
||||
since: "0.0.0",
|
||||
category: CATEGORY_PHP,
|
||||
type: "boolean",
|
||||
default: false,
|
||||
description: "Use single quotes instead of double quotes.",
|
||||
},
|
||||
};
|
||||
67
frontend/src/utils/prettier/plugins/php/src/parser.mjs
Normal file
67
frontend/src/utils/prettier/plugins/php/src/parser.mjs
Normal file
@@ -0,0 +1,67 @@
|
||||
import engine from "php-parser";
|
||||
import { LATEST_SUPPORTED_PHP_VERSION } from "./options.mjs";
|
||||
import { resolvePhpVersion } from "./options.mjs";
|
||||
|
||||
function parse(text, opts) {
|
||||
const inMarkdown = opts && opts.parentParser === "markdown";
|
||||
|
||||
if (!text && inMarkdown) {
|
||||
return "";
|
||||
}
|
||||
resolvePhpVersion(opts);
|
||||
|
||||
// Todo https://github.com/glayzzle/php-parser/issues/170
|
||||
text = text.replace(/\?>\n<\?/g, "?>\n___PSEUDO_INLINE_PLACEHOLDER___<?");
|
||||
|
||||
// initialize a new parser instance
|
||||
const parser = new engine({
|
||||
parser: {
|
||||
extractDoc: true,
|
||||
version: `${LATEST_SUPPORTED_PHP_VERSION}`,
|
||||
},
|
||||
ast: {
|
||||
withPositions: true,
|
||||
withSource: true,
|
||||
},
|
||||
});
|
||||
|
||||
const hasOpenPHPTag = text.indexOf("<?php") !== -1;
|
||||
const parseAsEval = inMarkdown && !hasOpenPHPTag;
|
||||
|
||||
let ast;
|
||||
try {
|
||||
ast = parseAsEval ? parser.parseEval(text) : parser.parseCode(text);
|
||||
} catch (err) {
|
||||
if (err instanceof SyntaxError && "lineNumber" in err) {
|
||||
err.loc = {
|
||||
start: {
|
||||
line: err.lineNumber,
|
||||
column: err.columnNumber,
|
||||
},
|
||||
};
|
||||
|
||||
delete err.lineNumber;
|
||||
delete err.columnNumber;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
ast.extra = {
|
||||
parseAsEval,
|
||||
};
|
||||
|
||||
// https://github.com/glayzzle/php-parser/issues/155
|
||||
// currently inline comments include the line break at the end, we need to
|
||||
// strip those out and update the end location for each comment manually
|
||||
ast.comments.forEach((comment) => {
|
||||
if (comment.value[comment.value.length - 1] === "\n") {
|
||||
comment.value = comment.value.slice(0, -1);
|
||||
comment.loc.end.offset = comment.loc.end.offset - 1;
|
||||
}
|
||||
});
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
export default parse;
|
||||
92
frontend/src/utils/prettier/plugins/php/src/pragma.mjs
Normal file
92
frontend/src/utils/prettier/plugins/php/src/pragma.mjs
Normal file
@@ -0,0 +1,92 @@
|
||||
import { memoize } from "./util.mjs";
|
||||
import parse from "./parser.mjs";
|
||||
|
||||
const reHasPragma = /@prettier|@format/;
|
||||
|
||||
const getPageLevelDocBlock = memoize((text) => {
|
||||
const parsed = parse(text);
|
||||
|
||||
const [firstChild] = parsed.children;
|
||||
const [firstDocBlock] = parsed.comments.filter(
|
||||
(el) => el.kind === "commentblock"
|
||||
);
|
||||
|
||||
if (
|
||||
firstChild &&
|
||||
firstDocBlock &&
|
||||
firstDocBlock.loc.start.line < firstChild.loc.start.line
|
||||
) {
|
||||
return firstDocBlock;
|
||||
}
|
||||
});
|
||||
|
||||
function hasPragma(text) {
|
||||
// fast path optimization - check if the pragma shows up in the file at all
|
||||
if (!reHasPragma.test(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pageLevelDocBlock = getPageLevelDocBlock(text);
|
||||
|
||||
if (pageLevelDocBlock) {
|
||||
const { value } = pageLevelDocBlock;
|
||||
|
||||
return reHasPragma.test(value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function injectPragma(docblock) {
|
||||
let lines = docblock.split("\n");
|
||||
|
||||
if (lines.length === 1) {
|
||||
// normalize to multiline for simplicity
|
||||
const [, line] = /\/*\*\*(.*)\*\//.exec(lines[0]);
|
||||
|
||||
lines = ["/**", ` * ${line.trim()}`, " */"];
|
||||
}
|
||||
|
||||
// find the first @pragma
|
||||
// if there happens to be one on the opening line, just put it on the next line.
|
||||
const pragmaIndex = lines.findIndex((line) => /@\S/.test(line)) || 1;
|
||||
|
||||
// not found => index == -1, which conveniently will splice 1 from the end.
|
||||
lines.splice(pragmaIndex, 0, " * @format");
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function insertPragma(text) {
|
||||
const pageLevelDocBlock = getPageLevelDocBlock(text);
|
||||
|
||||
if (pageLevelDocBlock) {
|
||||
const {
|
||||
start: { offset: startOffset },
|
||||
end: { offset: endOffset },
|
||||
} = pageLevelDocBlock.loc;
|
||||
const before = text.substring(0, startOffset);
|
||||
const after = text.substring(endOffset);
|
||||
|
||||
return `${before}${injectPragma(pageLevelDocBlock.value, text)}${after}`;
|
||||
}
|
||||
|
||||
const openTag = "<?php";
|
||||
|
||||
if (!text.startsWith(openTag)) {
|
||||
// bail out
|
||||
return text;
|
||||
}
|
||||
|
||||
const splitAt = openTag.length;
|
||||
const phpTag = text.substring(0, splitAt);
|
||||
const after = text.substring(splitAt);
|
||||
|
||||
return `${phpTag}
|
||||
/**
|
||||
* @format
|
||||
*/
|
||||
${after}`;
|
||||
}
|
||||
|
||||
export { hasPragma, insertPragma };
|
||||
2899
frontend/src/utils/prettier/plugins/php/src/printer.mjs
Normal file
2899
frontend/src/utils/prettier/plugins/php/src/printer.mjs
Normal file
File diff suppressed because it is too large
Load Diff
743
frontend/src/utils/prettier/plugins/php/src/util.mjs
Normal file
743
frontend/src/utils/prettier/plugins/php/src/util.mjs
Normal file
@@ -0,0 +1,743 @@
|
||||
import { util as prettierUtil } from "prettier";
|
||||
import { locStart } from "./loc.mjs";
|
||||
|
||||
const { hasNewline, skipEverythingButNewLine, skipNewline } = prettierUtil;
|
||||
|
||||
function printNumber(rawNumber) {
|
||||
return (
|
||||
rawNumber
|
||||
.toLowerCase()
|
||||
// Remove unnecessary plus and zeroes from scientific notation.
|
||||
.replace(/^([+-]?[\d.]+e)(?:\+|(-))?0*(\d)/, "$1$2$3")
|
||||
// Remove unnecessary scientific notation (1e0).
|
||||
.replace(/^([+-]?[\d.]+)e[+-]?0+$/, "$1")
|
||||
// Make sure numbers always start with a digit.
|
||||
.replace(/^([+-])?\./, "$10.")
|
||||
// Remove extraneous trailing decimal zeroes.
|
||||
.replace(/(\.\d+?)0+(?=e|$)/, "$1")
|
||||
// Remove unnecessary .e notation
|
||||
.replace(/\.(?=e)/, "")
|
||||
);
|
||||
}
|
||||
|
||||
// http://php.net/manual/en/language.operators.precedence.php
|
||||
const PRECEDENCE = new Map(
|
||||
[
|
||||
["or"],
|
||||
["xor"],
|
||||
["and"],
|
||||
[
|
||||
"=",
|
||||
"+=",
|
||||
"-=",
|
||||
"*=",
|
||||
"**=",
|
||||
"/=",
|
||||
".=",
|
||||
"%=",
|
||||
"&=",
|
||||
"|=",
|
||||
"^=",
|
||||
"<<=",
|
||||
">>=",
|
||||
],
|
||||
["??"],
|
||||
["||"],
|
||||
["&&"],
|
||||
["|"],
|
||||
["^"],
|
||||
["&"],
|
||||
["==", "===", "!=", "!==", "<>", "<=>"],
|
||||
["<", ">", "<=", ">="],
|
||||
[">>", "<<"],
|
||||
["+", "-", "."],
|
||||
["*", "/", "%"],
|
||||
["!"],
|
||||
["instanceof"],
|
||||
["++", "--", "~"],
|
||||
["**"],
|
||||
].flatMap((operators, index) =>
|
||||
operators.map((operator) => [operator, index])
|
||||
)
|
||||
);
|
||||
function getPrecedence(operator) {
|
||||
return PRECEDENCE.get(operator);
|
||||
}
|
||||
|
||||
const equalityOperators = ["==", "!=", "===", "!==", "<>", "<=>"];
|
||||
const multiplicativeOperators = ["*", "/", "%"];
|
||||
const bitshiftOperators = [">>", "<<"];
|
||||
|
||||
function isBitwiseOperator(operator) {
|
||||
return (
|
||||
!!bitshiftOperators[operator] ||
|
||||
operator === "|" ||
|
||||
operator === "^" ||
|
||||
operator === "&"
|
||||
);
|
||||
}
|
||||
|
||||
function shouldFlatten(parentOp, nodeOp) {
|
||||
if (getPrecedence(nodeOp) !== getPrecedence(parentOp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ** is right-associative
|
||||
// x ** y ** z --> x ** (y ** z)
|
||||
if (parentOp === "**") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// x == y == z --> (x == y) == z
|
||||
if (
|
||||
equalityOperators.includes(parentOp) &&
|
||||
equalityOperators.includes(nodeOp)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// x * y % z --> (x * y) % z
|
||||
if (
|
||||
(nodeOp === "%" && multiplicativeOperators.includes(parentOp)) ||
|
||||
(parentOp === "%" && multiplicativeOperators.includes(nodeOp))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// x * y / z --> (x * y) / z
|
||||
// x / y * z --> (x / y) * z
|
||||
if (
|
||||
nodeOp !== parentOp &&
|
||||
multiplicativeOperators.includes(nodeOp) &&
|
||||
multiplicativeOperators.includes(parentOp)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// x << y << z --> (x << y) << z
|
||||
if (
|
||||
bitshiftOperators.includes(parentOp) &&
|
||||
bitshiftOperators.includes(nodeOp)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function nodeHasStatement(node) {
|
||||
return [
|
||||
"block",
|
||||
"program",
|
||||
"namespace",
|
||||
"class",
|
||||
"enum",
|
||||
"interface",
|
||||
"trait",
|
||||
"traituse",
|
||||
"declare",
|
||||
].includes(node.kind);
|
||||
}
|
||||
|
||||
function getBodyFirstChild({ body }) {
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
if (body.kind === "block") {
|
||||
body = body.children;
|
||||
}
|
||||
return body[0];
|
||||
}
|
||||
|
||||
function getNodeListProperty(node) {
|
||||
const body = node.children || node.body || node.adaptations;
|
||||
return Array.isArray(body) ? body : null;
|
||||
}
|
||||
|
||||
function getLast(arr) {
|
||||
if (arr.length > 0) {
|
||||
return arr[arr.length - 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getPenultimate(arr) {
|
||||
if (arr.length > 1) {
|
||||
return arr[arr.length - 2];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isFirstChildrenInlineNode(path) {
|
||||
const { node } = path;
|
||||
|
||||
if (node.kind === "program") {
|
||||
const children = getNodeListProperty(node);
|
||||
|
||||
if (!children || children.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return children[0].kind === "inline";
|
||||
}
|
||||
|
||||
if (node.kind === "switch") {
|
||||
if (!node.body) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const children = getNodeListProperty(node.body);
|
||||
|
||||
if (children.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [firstCase] = children;
|
||||
|
||||
if (!firstCase.body) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const firstCaseChildren = getNodeListProperty(firstCase.body);
|
||||
|
||||
if (firstCaseChildren.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return firstCaseChildren[0].kind === "inline";
|
||||
}
|
||||
|
||||
const firstChild = getBodyFirstChild(node);
|
||||
|
||||
if (!firstChild) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return firstChild.kind === "inline";
|
||||
}
|
||||
|
||||
function isDocNode(node) {
|
||||
return (
|
||||
node.kind === "nowdoc" ||
|
||||
(node.kind === "encapsed" && node.type === "heredoc")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Heredoc/Nowdoc nodes need a trailing linebreak if they
|
||||
* appear as function arguments or array elements
|
||||
*/
|
||||
function docShouldHaveTrailingNewline(path, recurse = 0) {
|
||||
const node = path.getNode(recurse);
|
||||
const parent = path.getNode(recurse + 1);
|
||||
const parentParent = path.getNode(recurse + 2);
|
||||
|
||||
if (!parent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
(parentParent &&
|
||||
["call", "new", "echo"].includes(parentParent.kind) &&
|
||||
!["call", "array"].includes(parent.kind)) ||
|
||||
parent.kind === "parameter"
|
||||
) {
|
||||
const lastIndex = parentParent.arguments.length - 1;
|
||||
const index = parentParent.arguments.indexOf(parent);
|
||||
|
||||
return index !== lastIndex;
|
||||
}
|
||||
|
||||
if (parentParent && parentParent.kind === "for") {
|
||||
const initIndex = parentParent.init.indexOf(parent);
|
||||
|
||||
if (initIndex !== -1) {
|
||||
return initIndex !== parentParent.init.length - 1;
|
||||
}
|
||||
|
||||
const testIndex = parentParent.test.indexOf(parent);
|
||||
|
||||
if (testIndex !== -1) {
|
||||
return testIndex !== parentParent.test.length - 1;
|
||||
}
|
||||
|
||||
const incrementIndex = parentParent.increment.indexOf(parent);
|
||||
|
||||
if (incrementIndex !== -1) {
|
||||
return incrementIndex !== parentParent.increment.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (parent.kind === "bin") {
|
||||
return (
|
||||
parent.left === node || docShouldHaveTrailingNewline(path, recurse + 1)
|
||||
);
|
||||
}
|
||||
|
||||
if (parent.kind === "case" && parent.test === node) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (parent.kind === "staticvariable") {
|
||||
const lastIndex = parentParent.variables.length - 1;
|
||||
const index = parentParent.variables.indexOf(parent);
|
||||
|
||||
return index !== lastIndex;
|
||||
}
|
||||
|
||||
if (parent.kind === "entry") {
|
||||
if (parent.key === node) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const lastIndex = parentParent.items.length - 1;
|
||||
const index = parentParent.items.indexOf(parent);
|
||||
|
||||
return index !== lastIndex;
|
||||
}
|
||||
|
||||
if (["call", "new"].includes(parent.kind)) {
|
||||
const lastIndex = parent.arguments.length - 1;
|
||||
const index = parent.arguments.indexOf(node);
|
||||
|
||||
return index !== lastIndex;
|
||||
}
|
||||
|
||||
if (parent.kind === "echo") {
|
||||
const lastIndex = parent.expressions.length - 1;
|
||||
const index = parent.expressions.indexOf(node);
|
||||
|
||||
return index !== lastIndex;
|
||||
}
|
||||
|
||||
if (parent.kind === "array") {
|
||||
const lastIndex = parent.items.length - 1;
|
||||
const index = parent.items.indexOf(node);
|
||||
|
||||
return index !== lastIndex;
|
||||
}
|
||||
|
||||
if (parent.kind === "retif") {
|
||||
return docShouldHaveTrailingNewline(path, recurse + 1);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function lineShouldEndWithSemicolon(path) {
|
||||
const { node, parent: parentNode } = path;
|
||||
if (!parentNode) {
|
||||
return false;
|
||||
}
|
||||
// for single line control structures written in a shortform (ie without a block),
|
||||
// we need to make sure the single body node gets a semicolon
|
||||
if (
|
||||
["for", "foreach", "while", "do", "if", "switch"].includes(
|
||||
parentNode.kind
|
||||
) &&
|
||||
node.kind !== "block" &&
|
||||
node.kind !== "if" &&
|
||||
(parentNode.body === node || parentNode.alternate === node)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (!nodeHasStatement(parentNode)) {
|
||||
return false;
|
||||
}
|
||||
if (node.kind === "echo" && node.shortForm) {
|
||||
return false;
|
||||
}
|
||||
if (node.kind === "traituse") {
|
||||
return !node.adaptations;
|
||||
}
|
||||
if (node.kind === "method" && node.isAbstract) {
|
||||
return true;
|
||||
}
|
||||
if (node.kind === "method") {
|
||||
const { parent } = path;
|
||||
if (parent && parent.kind === "interface") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return [
|
||||
"expressionstatement",
|
||||
"do",
|
||||
"usegroup",
|
||||
"classconstant",
|
||||
"propertystatement",
|
||||
"traitprecedence",
|
||||
"traitalias",
|
||||
"goto",
|
||||
"constantstatement",
|
||||
"enumcase",
|
||||
"global",
|
||||
"static",
|
||||
"echo",
|
||||
"unset",
|
||||
"return",
|
||||
"break",
|
||||
"continue",
|
||||
"throw",
|
||||
].includes(node.kind);
|
||||
}
|
||||
|
||||
function fileShouldEndWithHardline(path) {
|
||||
const { node } = path;
|
||||
const isProgramNode = node.kind === "program";
|
||||
const lastNode = node.children && getLast(node.children);
|
||||
|
||||
if (!isProgramNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lastNode && ["halt", "inline"].includes(lastNode.kind)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
lastNode &&
|
||||
(lastNode.kind === "declare" || lastNode.kind === "namespace")
|
||||
) {
|
||||
const lastNestedNode =
|
||||
lastNode.children.length > 0 && getLast(lastNode.children);
|
||||
|
||||
if (lastNestedNode && ["halt", "inline"].includes(lastNestedNode.kind)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function maybeStripLeadingSlashFromUse(name) {
|
||||
const nameWithoutLeadingSlash = name.replace(/^\\/, "");
|
||||
if (nameWithoutLeadingSlash.indexOf("\\") !== -1) {
|
||||
return nameWithoutLeadingSlash;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
function hasDanglingComments(node) {
|
||||
return (
|
||||
node.comments &&
|
||||
node.comments.some((comment) => !comment.leading && !comment.trailing)
|
||||
);
|
||||
}
|
||||
|
||||
function isLookupNode(node) {
|
||||
return (
|
||||
node.kind === "propertylookup" ||
|
||||
node.kind === "nullsafepropertylookup" ||
|
||||
node.kind === "staticlookup" ||
|
||||
node.kind === "offsetlookup"
|
||||
);
|
||||
}
|
||||
|
||||
function shouldPrintHardLineAfterStartInControlStructure(path) {
|
||||
const { node } = path;
|
||||
|
||||
if (["try", "catch"].includes(node.kind)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isFirstChildrenInlineNode(path);
|
||||
}
|
||||
|
||||
function shouldPrintHardLineBeforeEndInControlStructure(path) {
|
||||
const { node } = path;
|
||||
|
||||
if (["try", "catch"].includes(node.kind)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (node.kind === "switch") {
|
||||
const children = getNodeListProperty(node.body);
|
||||
|
||||
if (children.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const lastCase = getLast(children);
|
||||
|
||||
if (!lastCase.body) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const childrenInCase = getNodeListProperty(lastCase.body);
|
||||
|
||||
if (childrenInCase.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return childrenInCase[0].kind !== "inline";
|
||||
}
|
||||
|
||||
return !isFirstChildrenInlineNode(path);
|
||||
}
|
||||
|
||||
function getAlignment(text) {
|
||||
const lines = text.split("\n");
|
||||
const lastLine = lines.pop();
|
||||
|
||||
return lastLine.length - lastLine.trimLeft().length + 1;
|
||||
}
|
||||
|
||||
function isProgramLikeNode(node) {
|
||||
return ["program", "declare", "namespace"].includes(node.kind);
|
||||
}
|
||||
|
||||
function isReferenceLikeNode(node) {
|
||||
return [
|
||||
"name",
|
||||
"parentreference",
|
||||
"selfreference",
|
||||
"staticreference",
|
||||
].includes(node.kind);
|
||||
}
|
||||
|
||||
// Return `logical` value for `bin` node containing `||` or `&&` type otherwise return kind of node.
|
||||
// Require for grouping logical and binary nodes in right way.
|
||||
function getNodeKindIncludingLogical(node) {
|
||||
if (node.kind === "bin" && ["||", "&&"].includes(node.type)) {
|
||||
return "logical";
|
||||
}
|
||||
|
||||
return node.kind;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if string can safely be converted from double to single quotes and vice-versa, i.e.
|
||||
*
|
||||
* - no embedded variables ("foo $bar")
|
||||
* - no linebreaks
|
||||
* - no special characters like \n, \t, ...
|
||||
* - no octal/hex/unicode characters
|
||||
*
|
||||
* See https://php.net/manual/en/language.types.string.php#language.types.string.syntax.double
|
||||
*/
|
||||
function useDoubleQuote(node, options) {
|
||||
if (node.isDoubleQuote === options.singleQuote) {
|
||||
// We have a double quote and the user passed singleQuote:true, or the other way around.
|
||||
const rawValue = node.raw.slice(node.raw[0] === "b" ? 2 : 1, -1);
|
||||
const isComplex = rawValue.match(
|
||||
/\\([$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}|u{([0-9a-fA-F]+)})|\r?\n|'|"|\$/
|
||||
);
|
||||
return node.isDoubleQuote ? isComplex : !isComplex;
|
||||
}
|
||||
return node.isDoubleQuote;
|
||||
}
|
||||
|
||||
function hasEmptyBody(path, name = "body") {
|
||||
const { node } = path;
|
||||
|
||||
return (
|
||||
node[name] &&
|
||||
node[name].children &&
|
||||
node[name].children.length === 0 &&
|
||||
(!node[name].comments || node[name].comments.length === 0)
|
||||
);
|
||||
}
|
||||
|
||||
function isNextLineEmptyAfterNamespace(text, node) {
|
||||
let idx = locStart(node);
|
||||
idx = skipEverythingButNewLine(text, idx);
|
||||
idx = skipNewline(text, idx);
|
||||
return hasNewline(text, idx);
|
||||
}
|
||||
|
||||
function shouldPrintHardlineBeforeTrailingComma(lastElem) {
|
||||
if (
|
||||
lastElem.kind === "nowdoc" ||
|
||||
(lastElem.kind === "encapsed" && lastElem.type === "heredoc")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
lastElem.kind === "entry" &&
|
||||
(lastElem.value.kind === "nowdoc" ||
|
||||
(lastElem.value.kind === "encapsed" && lastElem.value.type === "heredoc"))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getAncestorCounter(path, typeOrTypes) {
|
||||
const types = [].concat(typeOrTypes);
|
||||
let counter = -1;
|
||||
let ancestorNode;
|
||||
while ((ancestorNode = path.getParentNode(++counter))) {
|
||||
if (types.indexOf(ancestorNode.kind) !== -1) {
|
||||
return counter;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function getAncestorNode(path, typeOrTypes) {
|
||||
const counter = getAncestorCounter(path, typeOrTypes);
|
||||
return counter === -1 ? null : path.getParentNode(counter);
|
||||
}
|
||||
|
||||
const magicMethods = [
|
||||
"__construct",
|
||||
"__destruct",
|
||||
"__call",
|
||||
"__callStatic",
|
||||
"__get",
|
||||
"__set",
|
||||
"__isset",
|
||||
"__unset",
|
||||
"__sleep",
|
||||
"__wakeup",
|
||||
"__toString",
|
||||
"__invoke",
|
||||
"__set_state",
|
||||
"__clone",
|
||||
"__debugInfo",
|
||||
];
|
||||
const magicMethodsMap = new Map(
|
||||
magicMethods.map((name) => [name.toLowerCase(), name])
|
||||
);
|
||||
|
||||
function normalizeMagicMethodName(name) {
|
||||
const loweredName = name.toLowerCase();
|
||||
|
||||
if (magicMethodsMap.has(loweredName)) {
|
||||
return magicMethodsMap.get(loweredName);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} kindsArray
|
||||
* @returns {(node: Node | Comment) => Boolean}
|
||||
*/
|
||||
function createTypeCheckFunction(kindsArray) {
|
||||
const kinds = new Set(kindsArray);
|
||||
return (node) => kinds.has(node?.kind);
|
||||
}
|
||||
|
||||
const isSingleWordType = createTypeCheckFunction([
|
||||
"variadicplaceholder",
|
||||
"namedargument",
|
||||
"nullkeyword",
|
||||
"identifier",
|
||||
"parameter",
|
||||
"variable",
|
||||
"variadic",
|
||||
"boolean",
|
||||
"literal",
|
||||
"number",
|
||||
"string",
|
||||
"clone",
|
||||
"cast",
|
||||
]);
|
||||
|
||||
const isArrayExpression = createTypeCheckFunction(["array"]);
|
||||
const isCallLikeExpression = createTypeCheckFunction([
|
||||
"nullsafepropertylookup",
|
||||
"propertylookup",
|
||||
"staticlookup",
|
||||
"offsetlookup",
|
||||
"call",
|
||||
"new",
|
||||
]);
|
||||
const isArrowFuncExpression = createTypeCheckFunction(["arrowfunc"]);
|
||||
|
||||
function getChainParts(node, prev = []) {
|
||||
const parts = prev;
|
||||
if (isCallLikeExpression(node)) {
|
||||
parts.push(node);
|
||||
}
|
||||
|
||||
if (!node.what) {
|
||||
return parts;
|
||||
}
|
||||
|
||||
return getChainParts(node.what, parts);
|
||||
}
|
||||
|
||||
function isSimpleCallArgument(node, depth = 2) {
|
||||
if (depth <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isChildSimple = (child) => isSimpleCallArgument(child, depth - 1);
|
||||
|
||||
if (isSingleWordType(node)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isArrayExpression(node)) {
|
||||
return node.items.every((x) => x === null || isChildSimple(x));
|
||||
}
|
||||
|
||||
if (isCallLikeExpression(node)) {
|
||||
const parts = getChainParts(node);
|
||||
parts.unshift();
|
||||
|
||||
return (
|
||||
parts.length <= depth &&
|
||||
parts.every((node) =>
|
||||
isLookupNode(node)
|
||||
? isChildSimple(node.offset)
|
||||
: node.arguments.every(isChildSimple)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (isArrowFuncExpression(node)) {
|
||||
return (
|
||||
node.arguments.length <= depth && node.arguments.every(isChildSimple)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function memoize(fn) {
|
||||
const cache = new Map();
|
||||
return (key) => {
|
||||
if (!cache.has(key)) {
|
||||
cache.set(key, fn(key));
|
||||
}
|
||||
return cache.get(key);
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
printNumber,
|
||||
getPrecedence,
|
||||
isBitwiseOperator,
|
||||
shouldFlatten,
|
||||
nodeHasStatement,
|
||||
getLast,
|
||||
getPenultimate,
|
||||
getBodyFirstChild,
|
||||
lineShouldEndWithSemicolon,
|
||||
fileShouldEndWithHardline,
|
||||
maybeStripLeadingSlashFromUse,
|
||||
hasDanglingComments,
|
||||
docShouldHaveTrailingNewline,
|
||||
isLookupNode,
|
||||
isFirstChildrenInlineNode,
|
||||
shouldPrintHardLineAfterStartInControlStructure,
|
||||
shouldPrintHardLineBeforeEndInControlStructure,
|
||||
getAlignment,
|
||||
isProgramLikeNode,
|
||||
isReferenceLikeNode,
|
||||
getNodeKindIncludingLogical,
|
||||
useDoubleQuote,
|
||||
hasEmptyBody,
|
||||
isNextLineEmptyAfterNamespace,
|
||||
shouldPrintHardlineBeforeTrailingComma,
|
||||
isDocNode,
|
||||
getAncestorNode,
|
||||
normalizeMagicMethodName,
|
||||
isSimpleCallArgument,
|
||||
memoize,
|
||||
};
|
||||
30
frontend/src/utils/prettier/plugins/sql/languages.js
Normal file
30
frontend/src/utils/prettier/plugins/sql/languages.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// SQL语言定义
|
||||
export const languages = [
|
||||
{
|
||||
name: "SQL",
|
||||
parsers: ["sql"],
|
||||
extensions: [
|
||||
".sql",
|
||||
".ddl",
|
||||
".dml",
|
||||
".hql",
|
||||
".psql",
|
||||
".plsql",
|
||||
".mysql",
|
||||
".mssql",
|
||||
".pgsql",
|
||||
".sqlite",
|
||||
".bigquery",
|
||||
".snowflake",
|
||||
".redshift",
|
||||
".db2",
|
||||
".n1ql",
|
||||
".cql"
|
||||
],
|
||||
filenames: [
|
||||
"*.sql",
|
||||
"*.ddl",
|
||||
"*.dml"
|
||||
]
|
||||
}
|
||||
];
|
||||
41
frontend/src/utils/prettier/plugins/sql/sql.d.ts
vendored
41
frontend/src/utils/prettier/plugins/sql/sql.d.ts
vendored
@@ -1,11 +1,40 @@
|
||||
import { Parser, Plugin } from "prettier";
|
||||
import { AST, Option } from 'node-sql-parser';
|
||||
import { Options, ParserOptions, Plugin } from 'prettier';
|
||||
import {
|
||||
FormatOptions,
|
||||
FormatOptionsWithLanguage,
|
||||
} from 'sql-formatter';
|
||||
|
||||
export declare const languages: Plugin["languages"];
|
||||
export declare const parsers: {
|
||||
sql: Parser;
|
||||
export type SqlBaseOptions = Option &
|
||||
Partial<
|
||||
| (FormatOptions & { dialect: string })
|
||||
| (FormatOptionsWithLanguage & { dialect?: never })
|
||||
> & {
|
||||
formatter?: 'sql-formatter' | 'node-sql-parser' | 'sql-cst';
|
||||
params?: string;
|
||||
paramTypes?: string;
|
||||
autoDetectDialect?: boolean;
|
||||
};
|
||||
|
||||
export type SqlOptions = ParserOptions<AST> & SqlBaseOptions;
|
||||
export type SqlFormatOptions = Options & SqlBaseOptions;
|
||||
export declare const languages: Plugin["languages"];
|
||||
|
||||
export declare const parsers: {
|
||||
sql: {
|
||||
parse(text: string, options?: SqlOptions): AST | string;
|
||||
astFormat: 'sql';
|
||||
locStart(): number;
|
||||
locEnd(): number;
|
||||
};
|
||||
};
|
||||
|
||||
export declare const printers: Plugin["printers"];
|
||||
|
||||
export declare const options: Plugin["options"];
|
||||
|
||||
declare const plugin: Plugin;
|
||||
export default plugin;
|
||||
export declare function detectDialect(sql: string): string;
|
||||
|
||||
declare const SqlPlugin: Plugin<AST | string>;
|
||||
|
||||
export default SqlPlugin;
|
||||
|
||||
@@ -1,60 +1,459 @@
|
||||
import { format } from 'sql-formatter';
|
||||
import { JSOX } from 'jsox';
|
||||
import nodeSqlParser from 'node-sql-parser';
|
||||
import {
|
||||
format,
|
||||
formatDialect,
|
||||
} from 'sql-formatter';
|
||||
import { detectDialect } from './detect.mjs';
|
||||
import { languages } from './languages.js';
|
||||
|
||||
// Languages
|
||||
export const languages = [{
|
||||
name: "SQL",
|
||||
parsers: ["sql"],
|
||||
extensions: [".sql"]
|
||||
}];
|
||||
const parser = new nodeSqlParser.Parser();
|
||||
|
||||
const SQL_FORMATTER = 'sql-formatter';
|
||||
const NODE_SQL_PARSER = 'node-sql-parser';
|
||||
|
||||
// Parsers
|
||||
export const parsers = {
|
||||
sql: {
|
||||
parse: (text) => text,
|
||||
astFormat: "sql-format",
|
||||
locStart: (node) => 0,
|
||||
locEnd: (node) => node.length
|
||||
}
|
||||
parse(text, options = {}) {
|
||||
const { formatter, type, database } = options;
|
||||
|
||||
return formatter === SQL_FORMATTER
|
||||
? text
|
||||
: parser.astify(text, { type, database });
|
||||
},
|
||||
astFormat: 'sql',
|
||||
locStart: () => -1,
|
||||
locEnd: () => -1,
|
||||
},
|
||||
};
|
||||
|
||||
// Printers
|
||||
export const printers = {
|
||||
"sql-format": {
|
||||
print: (path) => {
|
||||
const text = path.getValue();
|
||||
sql: {
|
||||
print(path, options = {}) {
|
||||
const value = path.node;
|
||||
const {
|
||||
formatter = SQL_FORMATTER,
|
||||
type,
|
||||
database,
|
||||
dialect,
|
||||
language,
|
||||
params,
|
||||
paramTypes,
|
||||
autoDetectDialect = true,
|
||||
...formatOptions
|
||||
} = options;
|
||||
|
||||
if (!text || typeof text !== 'string') {
|
||||
return text;
|
||||
}
|
||||
let formatted;
|
||||
|
||||
try {
|
||||
// 自动检测SQL方言
|
||||
const dialect = detectDialect(text);
|
||||
|
||||
// 格式化配置 - 使用固定的最佳实践配置
|
||||
const formatOptions = {
|
||||
language: dialect,
|
||||
tabWidth: 2,
|
||||
useTabs: true,
|
||||
keywordCase: 'upper',
|
||||
dataTypeCase: 'upper',
|
||||
functionCase: 'upper',
|
||||
identifierCase: 'preserve'
|
||||
if (typeof value === 'string') {
|
||||
// 准备sql-formatter选项
|
||||
const sqlFormatterOptions = {
|
||||
...formatOptions,
|
||||
params:
|
||||
params == null
|
||||
? undefined
|
||||
: JSOX.parse(params),
|
||||
paramTypes:
|
||||
paramTypes == null
|
||||
? undefined
|
||||
: JSOX.parse(paramTypes),
|
||||
};
|
||||
|
||||
return format(text, formatOptions);
|
||||
} catch (error) {
|
||||
return text;
|
||||
let finalLanguage = language;
|
||||
let finalDialect = dialect;
|
||||
|
||||
if (autoDetectDialect && !language && !dialect) {
|
||||
const detectedDialect = detectDialect(value);
|
||||
finalLanguage = detectedDialect;
|
||||
}
|
||||
|
||||
// 使用适当的格式化方法
|
||||
if (finalDialect != null) {
|
||||
// 使用formatDialect方法
|
||||
formatted = formatDialect(value, {
|
||||
...sqlFormatterOptions,
|
||||
dialect: JSOX.parse(finalDialect),
|
||||
});
|
||||
} else {
|
||||
// 使用format方法
|
||||
formatted = format(value, {
|
||||
...sqlFormatterOptions,
|
||||
language: finalLanguage,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 使用node-sql-parser进行格式化
|
||||
formatted = parser.sqlify(value, { type, database });
|
||||
}
|
||||
|
||||
return formatted + '\n';
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// 插件选项
|
||||
export const options = {
|
||||
formatter: {
|
||||
category: 'Config',
|
||||
type: 'choice',
|
||||
default: SQL_FORMATTER,
|
||||
description: 'Choose which formatter to be used',
|
||||
choices: [
|
||||
{
|
||||
value: SQL_FORMATTER,
|
||||
description: 'use `sql-formatter` as formatter',
|
||||
},
|
||||
{
|
||||
value: NODE_SQL_PARSER,
|
||||
description: 'use `node-sql-parser` as formatter',
|
||||
},
|
||||
],
|
||||
},
|
||||
autoDetectDialect: {
|
||||
category: 'Config',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Automatically detect SQL dialect if language/dialect is not specified',
|
||||
},
|
||||
dialect: {
|
||||
category: 'Config',
|
||||
type: 'string',
|
||||
description: 'SQL dialect for `sql-formatter` formatDialect()',
|
||||
},
|
||||
language: {
|
||||
category: 'Config',
|
||||
type: 'choice',
|
||||
default: 'sql',
|
||||
description: 'SQL dialect for `sql-formatter` format()',
|
||||
choices: [
|
||||
{
|
||||
value: 'sql',
|
||||
description: 'Standard SQL: https://en.wikipedia.org/wiki/SQL:2011',
|
||||
},
|
||||
{
|
||||
value: 'bigquery',
|
||||
description:
|
||||
'Google Standard SQL (Bigquery): https://cloud.google.com/bigquery',
|
||||
},
|
||||
{
|
||||
value: 'db2',
|
||||
description: 'IBM DB2: https://www.ibm.com/products/db2',
|
||||
},
|
||||
{
|
||||
value: 'db2i',
|
||||
description:
|
||||
'IBM DB2i (experimental): https://www.ibm.com/docs/en/i/7.5?topic=overview-db2-i',
|
||||
},
|
||||
{
|
||||
value: 'hive',
|
||||
description: 'Apache Hive: https://hive.apache.org',
|
||||
},
|
||||
{
|
||||
value: 'mariadb',
|
||||
description: 'MariaDB: https://mariadb.com',
|
||||
},
|
||||
{
|
||||
value: 'mysql',
|
||||
description: 'MySQL: https://www.mysql.com',
|
||||
},
|
||||
{
|
||||
value: 'n1ql',
|
||||
description:
|
||||
'Couchbase N1QL: https://www.couchbase.com/products/n1ql',
|
||||
},
|
||||
{
|
||||
value: 'plsql',
|
||||
description:
|
||||
'Oracle PL/SQL: https://www.oracle.com/database/technologies/appdev/plsql.html',
|
||||
},
|
||||
{
|
||||
value: 'postgresql',
|
||||
description: 'PostgreSQL: https://www.postgresql.org',
|
||||
},
|
||||
{
|
||||
value: 'redshift',
|
||||
description:
|
||||
'Amazon Redshift: https://docs.aws.amazon.com/redshift/latest/dg/cm_chap_SQLCommandRef.html',
|
||||
},
|
||||
{
|
||||
value: 'singlestoredb',
|
||||
description:
|
||||
'SingleStoreDB: https://docs.singlestore.com/db/v7.8/en/introduction/singlestore-documentation.html',
|
||||
},
|
||||
{
|
||||
value: 'snowflake',
|
||||
description: 'Snowflake: https://docs.snowflake.com',
|
||||
},
|
||||
{
|
||||
value: 'spark',
|
||||
description: 'Spark: https://spark.apache.org',
|
||||
},
|
||||
{
|
||||
value: 'sqlite',
|
||||
description: 'SQLite: https://www.sqlite.org',
|
||||
},
|
||||
{
|
||||
value: 'transactsql',
|
||||
description:
|
||||
'SQL Server Transact-SQL: https://docs.microsoft.com/en-us/sql/sql-server/',
|
||||
},
|
||||
{
|
||||
value: 'tsql',
|
||||
description:
|
||||
'SQL Server Transact-SQL: https://docs.microsoft.com/en-us/sql/sql-server/',
|
||||
},
|
||||
{
|
||||
value: 'trino',
|
||||
description: 'Trino: https://trino.io',
|
||||
},
|
||||
],
|
||||
},
|
||||
keywordCase: {
|
||||
category: 'Output',
|
||||
type: 'choice',
|
||||
default: 'preserve',
|
||||
description:
|
||||
'Converts reserved keywords to upper- or lowercase for `sql-formatter`',
|
||||
choices: [
|
||||
{
|
||||
value: 'preserve',
|
||||
description: 'preserves the original case of reserved keywords',
|
||||
},
|
||||
{
|
||||
value: 'upper',
|
||||
description: 'converts reserved keywords to uppercase',
|
||||
},
|
||||
{
|
||||
value: 'lower',
|
||||
description: 'converts reserved keywords to lowercase',
|
||||
},
|
||||
],
|
||||
},
|
||||
dataTypeCase: {
|
||||
category: 'Output',
|
||||
type: 'choice',
|
||||
default: 'preserve',
|
||||
description:
|
||||
'Converts data types to upper- or lowercase for `sql-formatter`',
|
||||
choices: [
|
||||
{
|
||||
value: 'preserve',
|
||||
description: 'preserves the original case of data types',
|
||||
},
|
||||
{
|
||||
value: 'upper',
|
||||
description: 'converts data types to uppercase',
|
||||
},
|
||||
{
|
||||
value: 'lower',
|
||||
description: 'converts data types to lowercase',
|
||||
},
|
||||
],
|
||||
},
|
||||
functionCase: {
|
||||
category: 'Output',
|
||||
type: 'choice',
|
||||
default: 'preserve',
|
||||
description:
|
||||
'Converts functions to upper- or lowercase for `sql-formatter`',
|
||||
choices: [
|
||||
{
|
||||
value: 'preserve',
|
||||
description: 'preserves the original case of functions',
|
||||
},
|
||||
{
|
||||
value: 'upper',
|
||||
description: 'converts functions to uppercase',
|
||||
},
|
||||
{
|
||||
value: 'lower',
|
||||
description: 'converts functions to lowercase',
|
||||
},
|
||||
],
|
||||
},
|
||||
identifierCase: {
|
||||
category: 'Output',
|
||||
type: 'choice',
|
||||
default: 'preserve',
|
||||
description:
|
||||
'Converts identifiers to upper- or lowercase for `sql-formatter`. Only unquoted identifiers are converted. (experimental)',
|
||||
choices: [
|
||||
{
|
||||
value: 'preserve',
|
||||
description: 'preserves the original case of identifiers',
|
||||
},
|
||||
{
|
||||
value: 'upper',
|
||||
description: 'converts identifiers to uppercase',
|
||||
},
|
||||
{
|
||||
value: 'lower',
|
||||
description: 'converts identifiers to lowercase',
|
||||
},
|
||||
],
|
||||
},
|
||||
uppercase: {
|
||||
category: 'Output',
|
||||
type: 'boolean',
|
||||
deprecated: '0.7.0',
|
||||
description: 'Use `keywordCase` option instead',
|
||||
},
|
||||
indentStyle: {
|
||||
category: 'Format',
|
||||
type: 'choice',
|
||||
default: 'standard',
|
||||
description: `Switches between different indentation styles for \`sql-formatter\`.
|
||||
|
||||
// Default export
|
||||
export default {
|
||||
Caveats of using \`"tabularLeft"\` and \`"tabularRight"\`:
|
||||
|
||||
- \`tabWidth\` option is ignored. Indentation will always be 10 spaces, regardless of what is specified by \`tabWidth\``,
|
||||
choices: [
|
||||
{
|
||||
value: 'standard',
|
||||
description:
|
||||
'indents code by the amount specified by `tabWidth` option',
|
||||
},
|
||||
{
|
||||
value: 'tabularLeft',
|
||||
description:
|
||||
'indents in tabular style with 10 spaces, aligning keywords to left',
|
||||
},
|
||||
{
|
||||
value: 'tabularRight',
|
||||
description:
|
||||
'indents in tabular style with 10 spaces, aligning keywords to right',
|
||||
},
|
||||
],
|
||||
},
|
||||
logicalOperatorNewline: {
|
||||
category: 'Format',
|
||||
type: 'choice',
|
||||
default: 'before',
|
||||
description:
|
||||
'Decides newline placement before or after logical operators (AND, OR, XOR)',
|
||||
choices: [
|
||||
{
|
||||
value: 'before',
|
||||
description: 'adds newline before the operator',
|
||||
},
|
||||
{
|
||||
value: 'after',
|
||||
description: 'adds newline after the operator',
|
||||
},
|
||||
],
|
||||
},
|
||||
expressionWidth: {
|
||||
category: 'Format',
|
||||
type: 'int',
|
||||
default: 50,
|
||||
description:
|
||||
'Determines maximum length of parenthesized expressions for `sql-formatter`',
|
||||
},
|
||||
linesBetweenQueries: {
|
||||
category: 'Format',
|
||||
type: 'int',
|
||||
default: 1,
|
||||
description:
|
||||
'Decides how many empty lines to leave between SQL statements for `sql-formatter`',
|
||||
},
|
||||
denseOperators: {
|
||||
category: 'Format',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Decides whitespace around operators for `sql-formatter`. Does not apply to logical operators (AND, OR, XOR).',
|
||||
},
|
||||
newlineBeforeSemicolon: {
|
||||
category: 'Format',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to place query separator (`;`) on a separate line for `sql-formatter`',
|
||||
},
|
||||
params: {
|
||||
category: 'Format',
|
||||
type: 'string',
|
||||
description:
|
||||
'Specifies `JSOX` **stringified** parameter values to fill in for placeholders inside SQL for `sql-formatter`. This option is designed to be used through API (though nothing really prevents usage from command line).',
|
||||
},
|
||||
paramTypes: {
|
||||
category: 'Config',
|
||||
type: 'string',
|
||||
description:
|
||||
'Specifies `JSOX` **stringified** parameter types to support when parsing SQL prepared statements for `sql-formatter`.',
|
||||
},
|
||||
type: {
|
||||
category: 'Config',
|
||||
type: 'choice',
|
||||
default: 'table',
|
||||
description: 'Check the SQL with Authority List for `node-sql-parser`',
|
||||
choices: [
|
||||
{
|
||||
value: 'table',
|
||||
description: '`table` mode',
|
||||
},
|
||||
{
|
||||
value: 'column',
|
||||
description: '`column` mode',
|
||||
},
|
||||
],
|
||||
},
|
||||
database: {
|
||||
category: 'Config',
|
||||
type: 'choice',
|
||||
default: 'mysql',
|
||||
description: 'SQL dialect for `node-sql-parser`',
|
||||
choices: [
|
||||
{
|
||||
value: 'bigquery',
|
||||
description: 'BigQuery: https://cloud.google.com/bigquery',
|
||||
},
|
||||
{
|
||||
value: 'db2',
|
||||
description: 'IBM DB2: https://www.ibm.com/analytics/db2',
|
||||
},
|
||||
{
|
||||
value: 'hive',
|
||||
description: 'Hive: https://hive.apache.org',
|
||||
},
|
||||
{
|
||||
value: 'mariadb',
|
||||
description: 'MariaDB: https://mariadb.com',
|
||||
},
|
||||
{
|
||||
value: 'mysql',
|
||||
description: 'MySQL: https://www.mysql.com',
|
||||
},
|
||||
{
|
||||
value: 'postgresql',
|
||||
description: 'PostgreSQL: https://www.postgresql.org',
|
||||
},
|
||||
{
|
||||
value: 'transactsql',
|
||||
description:
|
||||
'TransactSQL: https://docs.microsoft.com/en-us/sql/t-sql',
|
||||
},
|
||||
{
|
||||
value: 'flinksql',
|
||||
description:
|
||||
'FlinkSQL: https://ci.apache.org/projects/flink/flink-docs-stable',
|
||||
},
|
||||
{
|
||||
value: 'snowflake',
|
||||
description: 'Snowflake (alpha): https://docs.snowflake.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const SqlPlugin = {
|
||||
languages,
|
||||
parsers,
|
||||
printers
|
||||
printers,
|
||||
options,
|
||||
};
|
||||
|
||||
export { languages };
|
||||
export default SqlPlugin;
|
||||
@@ -38,6 +38,7 @@ import markdownPrettierPlugin from "prettier/plugins/markdown"
|
||||
import yamlPrettierPlugin from "prettier/plugins/yaml"
|
||||
import goPrettierPlugin from "@/utils/prettier/plugins/go/go"
|
||||
import sqlPrettierPlugin from "@/utils/prettier/plugins/sql/sql"
|
||||
import phpPrettierPlugin from "@/utils/prettier/plugins/php"
|
||||
import * as prettierPluginEstree from "prettier/plugins/estree";
|
||||
|
||||
/**
|
||||
@@ -78,7 +79,10 @@ export const LANGUAGES: LanguageInfo[] = [
|
||||
plugins: [markdownPrettierPlugin]
|
||||
}),
|
||||
new LanguageInfo("java", "Java", javaLanguage.parser),
|
||||
new LanguageInfo("php", "PHP", phpLanguage.configure({top: "Program"}).parser),
|
||||
new LanguageInfo("php", "PHP", phpLanguage.configure({top: "Program"}).parser, {
|
||||
parser: "php",
|
||||
plugins: [phpPrettierPlugin]
|
||||
}),
|
||||
new LanguageInfo("css", "CSS", cssLanguage.parser, {
|
||||
parser: "css",
|
||||
plugins: [cssPrettierPlugin]
|
||||
|
||||
@@ -2,6 +2,7 @@ import {defineConfig, loadEnv} from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import * as path from 'path';
|
||||
import { nodePolyfills } from 'vite-plugin-node-polyfills'
|
||||
|
||||
export default defineConfig(({mode}: { mode: string }): object => {
|
||||
const env: Record<string, string> = loadEnv(mode, process.cwd());
|
||||
@@ -15,6 +16,7 @@ export default defineConfig(({mode}: { mode: string }): object => {
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
nodePolyfills(),
|
||||
Components({
|
||||
dts: true,
|
||||
dirs: ['src/components'],
|
||||
|
||||
Reference in New Issue
Block a user