diff --git a/package.json b/package.json index 154b2439..9eac2601 100644 --- a/package.json +++ b/package.json @@ -116,8 +116,7 @@ "npm-run-all": "^4.1.5", "parcel-bundler": "^1.12.5", "parcel-plugin-inliner": "^1.0.16", - "php-array-parser": "^1.0.1", - "php-parser": "^3.0.3", + "php-parser": "^3.2.2", "pug": "^3.0.3", "rimraf": "^3.0.2", "standard-version": "^9.3.1", diff --git a/src/parsers/json.ts b/src/parsers/json.ts index af8a30cb..ba7732ac 100644 --- a/src/parsers/json.ts +++ b/src/parsers/json.ts @@ -1,38 +1,43 @@ -import SortedStringify from 'json-stable-stringify' +import SortedStringify from "json-stable-stringify"; // @ts-ignore -import JsonMap from 'json-source-map' -import { Parser } from './base' +import JsonMap from "json-source-map"; +import { Parser } from "./base"; export class JsonParser extends Parser { - id = 'json' + id = "json"; constructor() { - super(['json'], 'json') + super(["json"], "json"); } async parse(text: string) { - if (!text || !text.trim()) - return {} - return JSON.parse(text) + if (!text || !text.trim()) return {}; + return JSON.parse(text); } - async dump(object: object, sort: boolean, compare: ((x: string, y: string) => number) | undefined) { - const indent = this.options.tab === '\t' ? this.options.tab : this.options.indent + async dump( + object: object, + sort: boolean, + compare: ((x: string, y: string) => number) | undefined + ) { + const indent = + this.options.tab === "\t" ? this.options.tab : this.options.indent; if (sort) - return `${SortedStringify(object, { space: indent, cmp: compare ? (a, b) => compare(a.key, b.key) : undefined })}\n` - else - return `${JSON.stringify(object, null, indent)}\n` + return `${SortedStringify(object, { + space: indent, + cmp: compare ? (a, b) => compare(a.key, b.key) : undefined + })}\n`; + else return `${JSON.stringify(object, null, indent)}\n`; } - annotationSupported = true - annotationLanguageIds = ['json'] + annotationSupported = true; + annotationLanguageIds = ["json"]; parseAST(text: string) { - if (!text || !text.trim()) - return [] + if (!text || !text.trim()) return []; - const map = JsonMap.parse(text).pointers + const map = JsonMap.parse(text).pointers; const pairs = Object.entries(map) .filter(([k, v]) => k) .map(([k, v]) => ({ @@ -40,12 +45,13 @@ export class JsonParser extends Parser { start: v.value.pos + 1, end: v.valueEnd.pos - 1, // https://tools.ietf.org/html/rfc6901 - key: k.slice(1) - .replace(/\//g, '.') - .replace(/~0/g, '~') - .replace(/~1/g, '/'), - })) - - return pairs + key: k + .slice(1) + .replace(/\//g, ".") + .replace(/~0/g, "~") + .replace(/~1/g, "/") + })); + + return pairs; } } diff --git a/src/parsers/php.ts b/src/parsers/php.ts index 6853a7b9..4df88c47 100644 --- a/src/parsers/php.ts +++ b/src/parsers/php.ts @@ -1,10 +1,28 @@ // @ts-ignore -import parser from 'php-array-parser' +import { + Array as PhpArray, + Engine, + Entry, + Node, + Return, + String, +} from 'php-parser' import { Parser } from './base' +import { KeyInDocument } from '~/core' export class PhpParser extends Parser { id = 'php' - readonly = true + readonly = false + phpParser: Engine = new Engine({ + // some options : + parser: { + extractDoc: true, + php7: true, + }, + ast: { + withPositions: true, + }, + }) constructor() { super(['php'], 'php') @@ -12,13 +30,168 @@ export class PhpParser extends Parser { async parse(text: string) { // Remove left part of return expression and any ending `?>`. - const ret = text.indexOf('return') + 'return'.length - text = text.substr(ret) - text = text.replace(/\?>\s*$/, '_') - return parser.parse(text) + const programme = this.phpParser.parseCode(text, '') + if (!programme.children.length && programme.children[0].kind != 'return') + return {} + + const returnChild = programme.children[0] as Return + if (returnChild.expr?.kind != 'array') + return {} + + const returnExpression = returnChild.expr + + return this.parsePhpArray(returnExpression) + } + + async dump( + object: object, + sort: boolean, + compare: ((x: string, y: string) => number) | undefined, + ) { + const indent + = this.options.tab === '\t' + ? this.options.tab + : ' '.repeat(this.options.indent) + + return ` { + const entery = item as Entry + if (item.kind != 'entry') + return false + + if (entery.key?.kind != 'string') + return false + + if (entery.value?.kind != 'string' && entery.value?.kind != 'array') + return false + + if (!entery.value.loc) + return false + + return true + }) + .map((item): KeyInDocument | KeyInDocument[] => { + const entery = item as Entry + const keyString = ((entery.key as unknown) as String).value + + if (entery.value.kind === 'array') + return this.phpToAST(entery.value, keyString) + + return { + quoted: true, + key: parent ? `${parent}.${keyString}` : keyString, + start: entery.value.loc?.start.offset ?? 0, + end: entery.value.loc?.end.offset ?? 0, + } + }) + .flatMap(item => (Array.isArray(item) ? item : [item])) } - async dump() { + private parsePhpArray(initialData: Node) { + if (initialData.kind != 'array') + return {} + + const returnValue = {} as Record + const array = initialData as PhpArray + + array.items.forEach((item) => { + const entery = item as Entry + + if (item.kind != 'entry') + return + + if (entery.key?.kind != 'string') + return + + const keyString = ((entery.key as unknown) as String).value + + if (entery.value?.kind != 'string' && entery.value?.kind != 'array') + return + + if (entery.value.kind === 'string') + returnValue[keyString] = ((entery.value as unknown) as String).value + else if (entery.value.kind === 'array') + returnValue[keyString] = this.parsePhpArray(entery.value) + }) + + return returnValue + } + + private arrayToPhp( + object: object, + identEl: string, + indent: number, + sort: boolean, + compare: ((x: string, y: string) => number) | undefined, + ): string { + if (typeof object === 'string') + return `"${object}"` + + if (typeof object === 'boolean') + return object ? 'true' : 'false' + + if (typeof object === 'number') + return object + + const indentation = identEl.repeat(indent) + + if (typeof object === 'object') { + const entries = Object.entries(object) + if (sort) { + entries.sort(([keyA], [keyB]) => + compare ? compare(keyA, keyB) : keyA.localeCompare(keyB), + ) + } + + return ( + `[\n${ + entries + .map( + ([key, value]) => + `${indentation} "${key}" => ${this.arrayToPhp( + value, + identEl, + indent + 1, + sort, + compare, + )}`, + ) + .join(',\n') + }\n${ + indentation + }]` + ) + } + return '' } } diff --git a/yarn.lock b/yarn.lock index 246a65b8..6ec449e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8411,22 +8411,10 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -php-array-parser@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/php-array-parser/-/php-array-parser-1.0.1.tgz#9da12683c3bebbe228a7e238c716435437631eaa" - integrity sha512-elnanzvSd/+U2eWD9Hp2MIab6LzWlnp9J+sTrQasDoR6VsWMDa3fDYBS8pojvu3RwPbpgDUHUxV5WGhfJ1NewQ== - dependencies: - php-parser "^2.0.3" - -php-parser@^2.0.3: - version "2.2.0" - resolved "https://registry.yarnpkg.com/php-parser/-/php-parser-2.2.0.tgz#67384f0a5933dbbef40beab0ab31d0b8c582ff88" - integrity sha1-ZzhPClkz2770C+qwqzHQuMWC/4g= - -php-parser@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/php-parser/-/php-parser-3.0.3.tgz#287779ea8a273ec5cf926f28e5f38ffa4ad32d4a" - integrity sha512-WjbrtYrwmLY9hpoKoq1+mVqJhT0dEVDZRWSpNIw2MpTw3VM/K4C6e0WR4KlU6G/XROkV7tpH4NesV2dDiPxqaw== +php-parser@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/php-parser/-/php-parser-3.2.2.tgz#49c6fac29fbf8f995e1b8609ad52bd41b1ecda07" + integrity sha512-voj3rzCJmEbwHwH3QteON28wA6K+JbcaJEofyUZkUXmcViiXofjbSbcE5PtqtjX6nstnnAEYCFoRq0mkjP5/cg== physical-cpu-count@^2.0.0: version "2.0.0"