13 Commits

Author SHA1 Message Date
f72010bd69 Added clang prettier plugin 2025-09-19 19:17:13 +08:00
cd027097f8 🐛 Fixed golang prettier plugin issue 2025-09-19 18:39:41 +08:00
9cbbf729c0 🔥 Remove powershell prettier plugin 2025-09-18 00:13:07 +08:00
landaiqing
c26c11e253 🐛 Fixed build issue 2025-09-17 09:54:43 +08:00
338ac358db 🚧 Modify toml,powershell prettier plugin(beta) 2025-09-17 00:12:39 +08:00
a83c7139c9 Added scala、powershell、groovy prettier plugin 2025-09-14 23:45:01 +08:00
42c7d11c09 📦 Optimized packaging 2025-09-13 20:25:19 +08:00
5ca5aa64c7 Added shell prettier plugin 2025-09-13 19:21:06 +08:00
eda7ef771e Added rust prettier plugin 2025-09-13 00:02:17 +08:00
593c4d7783 Added java prettier plugin 2025-09-12 23:01:19 +08:00
d24a522b32 Added php prettier plugin 2025-09-12 20:15:56 +08:00
41afb834ae Added sql prettier plugin 2025-09-12 00:52:19 +08:00
b745329e26 Added golang prettier plugin 2025-09-11 20:42:39 +08:00
134 changed files with 35378 additions and 424 deletions

View File

@@ -18,7 +18,6 @@ import * as ThemeService from "./themeservice.js";
import * as TranslationService from "./translationservice.js";
import * as TrayService from "./trayservice.js";
import * as WindowService from "./windowservice.js";
import * as WindowSnapService from "./windowsnapservice.js";
export {
BackupService,
ConfigService,
@@ -36,8 +35,7 @@ export {
ThemeService,
TranslationService,
TrayService,
WindowService,
WindowSnapService
WindowService
};
export * from "./models.js";

View File

@@ -1,79 +0,0 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
/**
* WindowSnapService 窗口吸附服务
* @module
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
/**
* Cleanup 清理资源
*/
export function Cleanup(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2155505498) as any;
return $resultPromise;
}
/**
* GetCurrentThreshold 获取当前自适应阈值(用于调试或显示)
*/
export function GetCurrentThreshold(): Promise<number> & { cancel(): void } {
let $resultPromise = $Call.ByID(3176419026) as any;
return $resultPromise;
}
/**
* OnWindowSnapConfigChanged 处理窗口吸附配置变更
*/
export function OnWindowSnapConfigChanged(enabled: boolean): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3794787039, enabled) as any;
return $resultPromise;
}
/**
* RegisterWindow 注册需要吸附管理的窗口
*/
export function RegisterWindow(documentID: number, window: application$0.WebviewWindow | null, title: string): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1000222723, documentID, window, title) as any;
return $resultPromise;
}
/**
* ServiceShutdown 实现服务关闭接口
*/
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1172710495) as any;
return $resultPromise;
}
/**
* SetAppReferences 设置应用和主窗口引用
*/
export function SetAppReferences(app: application$0.App | null, mainWindow: application$0.WebviewWindow | null): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1782093351, app, mainWindow) as any;
return $resultPromise;
}
/**
* SetSnapEnabled 设置是否启用窗口吸附
*/
export function SetSnapEnabled(enabled: boolean): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2280126835, enabled) as any;
return $resultPromise;
}
/**
* UnregisterWindow 取消注册窗口
*/
export function UnregisterWindow(documentID: number): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2844230768, documentID) as any;
return $resultPromise;
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,20 +5,20 @@
"type": "module",
"scripts": {
"dev": "vite --host --mode development",
"build:dev": "vue-tsc && vite build --minify false --mode development",
"build": "vue-tsc && vite build --mode production",
"build:dev": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" vue-tsc && cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" vite build --minify false --mode development",
"build": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" vue-tsc && cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" vite build --mode production",
"preview": "vite preview",
"lint": "eslint",
"lint:fix": "eslint --fix"
},
"dependencies": {
"@codemirror/autocomplete": "^6.18.6",
"@codemirror/autocomplete": "^6.18.7",
"@codemirror/commands": "^6.8.1",
"@codemirror/lang-angular": "^0.1.4",
"@codemirror/lang-cpp": "^6.0.3",
"@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-go": "^6.0.1",
"@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-html": "^6.4.10",
"@codemirror/lang-java": "^6.0.2",
"@codemirror/lang-javascript": "^6.2.4",
"@codemirror/lang-json": "^6.0.2",
@@ -33,7 +33,6 @@
"@codemirror/lang-sql": "^6.9.1",
"@codemirror/lang-vue": "^0.1.3",
"@codemirror/lang-wast": "^6.0.2",
"@codemirror/lang-xml": "^6.1.0",
"@codemirror/lang-yaml": "^6.1.2",
"@codemirror/language": "^6.11.3",
"@codemirror/language-data": "^6.5.1",
@@ -41,40 +40,56 @@
"@codemirror/lint": "^6.8.5",
"@codemirror/search": "^6.5.11",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.38.1",
"@codemirror/view": "^6.38.2",
"@cospaia/prettier-plugin-clojure": "^0.0.2",
"@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.2",
"@prettier/plugin-xml": "^3.4.2",
"@reteps/dockerfmt": "^0.3.6",
"@toml-tools/lexer": "^1.0.0",
"@toml-tools/parser": "^1.0.0",
"codemirror": "^6.0.2",
"codemirror-lang-elixir": "^4.0.0",
"colors-named": "^1.0.2",
"colors-named-hex": "^1.0.2",
"franc-min": "^6.2.0",
"groovy-beautify": "^0.0.17",
"hsl-matcher": "^1.2.4",
"lezer": "^0.13.5",
"java-parser": "^3.0.1",
"jinx-rust": "^0.1.6",
"jsox": "^1.2.123",
"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",
"remarkable": "^2.0.1",
"sass": "^1.90.0",
"vue": "^3.5.19",
"vue-i18n": "^11.1.11",
"sass": "^1.92.1",
"sh-syntax": "^0.5.8",
"sql-formatter": "^15.6.9",
"vue": "^3.5.21",
"vue-i18n": "^11.1.12",
"vue-pick-colors": "^1.8.0",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@eslint/js": "^9.34.0",
"@eslint/js": "^9.35.0",
"@lezer/generator": "^1.8.0",
"@types/node": "^24.3.0",
"@types/node": "^24.3.1",
"@types/remarkable": "^2.0.8",
"@vitejs/plugin-vue": "^6.0.1",
"@wailsio/runtime": "latest",
"eslint": "^9.34.0",
"cross-env": "^7.0.3",
"eslint": "^9.35.0",
"eslint-plugin-vue": "^10.4.0",
"globals": "^16.3.0",
"globals": "^16.4.0",
"rollup-plugin-visualizer": "^6.0.3",
"typescript": "^5.9.2",
"typescript-eslint": "^8.40.0",
"typescript-eslint": "^8.43.0",
"unplugin-vue-components": "^29.0.0",
"vite": "^7.1.3",
"vite": "^7.1.5",
"vite-plugin-node-polyfills": "^0.24.0",
"vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.0.6"
}

Binary file not shown.

View File

@@ -0,0 +1,117 @@
#include "CustomFileSystem.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Path.h"
#include <emscripten.h>
#include <iostream>
#include <string>
#include <system_error>
using namespace llvm;
using namespace llvm::vfs;
namespace {
bool isRunningOnWindows() {
return EM_ASM_INT({return process.platform == 'win32' ? 1 : 0}) == 1;
}
std::error_code current_path(SmallVectorImpl<char> &result) {
result.clear();
const char *pwd = ::getenv("PWD");
result.append(pwd, pwd + strlen(pwd));
return {};
}
} // namespace
namespace llvm {
namespace vfs {
sys::path::Style getPathStyle() {
static sys::path::Style cachedStyle = sys::path::Style::native;
if (cachedStyle == sys::path::Style::native) {
cachedStyle = isRunningOnWindows() ? sys::path::Style::windows
: sys::path::Style::posix;
}
return cachedStyle;
}
void make_absolute(const Twine &current_directory,
SmallVectorImpl<char> &path) {
StringRef p(path.data(), path.size());
auto pathStyle = getPathStyle();
bool rootDirectory = sys::path::has_root_directory(p, pathStyle);
bool rootName = sys::path::has_root_name(p, pathStyle);
// Already absolute.
if ((rootName || is_style_posix(pathStyle)) && rootDirectory)
return;
// All of the following conditions will need the current directory.
SmallString<128> current_dir;
current_directory.toVector(current_dir);
// Relative path. Prepend the current directory.
if (!rootName && !rootDirectory) {
// Append path to the current directory.
sys::path::append(current_dir, pathStyle, p);
// Set path to the result.
path.swap(current_dir);
return;
}
if (!rootName && rootDirectory) {
StringRef cdrn = sys::path::root_name(current_dir, pathStyle);
SmallString<128> curDirRootName(cdrn.begin(), cdrn.end());
sys::path::append(curDirRootName, pathStyle, p);
// Set path to the result.
path.swap(curDirRootName);
return;
}
if (rootName && !rootDirectory) {
StringRef pRootName = sys::path::root_name(p, pathStyle);
StringRef bRootDirectory =
sys::path::root_directory(current_dir, pathStyle);
StringRef bRelativePath = sys::path::relative_path(current_dir, pathStyle);
StringRef pRelativePath = sys::path::relative_path(p, pathStyle);
SmallString<128> res;
sys::path::append(res, pathStyle, pRootName, bRootDirectory, bRelativePath,
pRelativePath);
path.swap(res);
return;
}
llvm_unreachable("All rootName and rootDirectory combinations should have "
"occurred above!");
}
std::error_code make_absolute(SmallVectorImpl<char> &path) {
if (sys::path::is_absolute(path, getPathStyle()))
return {};
SmallString<128> current_dir;
if (std::error_code ec = current_path(current_dir))
return ec;
make_absolute(current_dir, path);
return {};
}
CustomFileSystem::CustomFileSystem(IntrusiveRefCntPtr<FileSystem> FS)
: ProxyFileSystem(std::move(FS)) {}
std::error_code
CustomFileSystem::makeAbsolute(SmallVectorImpl<char> &Path) const {
return make_absolute(Path);
}
} // namespace vfs
} // namespace llvm

View File

@@ -0,0 +1,27 @@
#ifndef CUSTOM_FILE_SYSTEM_H
#define CUSTOM_FILE_SYSTEM_H
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/VirtualFileSystem.h"
namespace llvm {
namespace vfs {
sys::path::Style getPathStyle();
std::error_code make_absolute(SmallVectorImpl<char> &path);
class CustomFileSystem : public ProxyFileSystem {
public:
CustomFileSystem(IntrusiveRefCntPtr<FileSystem> FS);
std::error_code makeAbsolute(SmallVectorImpl<char> &Path) const override;
};
} // namespace vfs
} // namespace llvm
#endif // CUSTOM_FILE_SYSTEM_H

View File

@@ -0,0 +1,26 @@
#include "lib.h"
#include <emscripten/bind.h>
using namespace emscripten;
EMSCRIPTEN_BINDINGS(my_module) {
register_vector<unsigned>("RangeList");
value_object<Result>("Result")
.field("error", &Result::error)
.field("content", &Result::content);
function<std::string>("version", &version);
function<Result, const std::string, const std::string, const std::string>(
"format", &format);
function<Result, const std::string, const std::string, const std::string,
const std::vector<unsigned>>("format_byte", &format_byte);
function<Result, const std::string, const std::string, const std::string,
const std::vector<unsigned>>("format_line", &format_line);
function<void, const std::string>("set_fallback_style", &set_fallback_style);
function<void, bool>("set_sort_includes", &set_sort_includes);
function<Result, const std::string, const std::string, const std::string>(
"dump_config", &dump_config);
}
int main(void) {}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,197 @@
#!/usr/bin/env python3
#
# ===- clang-format-diff.py - ClangFormat Diff Reformatter ----*- python -*--===#
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# ===------------------------------------------------------------------------===#
"""
This script reads input from a unified diff and reformats all the changed
lines. This is useful to reformat all the lines touched by a specific patch.
Example usage for git/svn users:
git diff -U0 --no-color --relative HEAD^ | {clang_format_diff} -p1 -i
svn diff --diff-cmd=diff -x-U0 | {clang_format_diff} -i
It should be noted that the filename contained in the diff is used unmodified
to determine the source file to update. Users calling this script directly
should be careful to ensure that the path in the diff is correct relative to the
current working directory.
"""
from __future__ import absolute_import, division, print_function
import argparse
import difflib
import re
import subprocess
import sys
if sys.version_info.major >= 3:
from io import StringIO
else:
from io import BytesIO as StringIO
def main():
parser = argparse.ArgumentParser(
description=__doc__.format(clang_format_diff="%(prog)s"),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"-i",
action="store_true",
default=False,
help="apply edits to files instead of displaying a diff",
)
parser.add_argument(
"-p",
metavar="NUM",
default=0,
help="strip the smallest prefix containing P slashes",
)
parser.add_argument(
"-regex",
metavar="PATTERN",
default=None,
help="custom pattern selecting file paths to reformat "
"(case sensitive, overrides -iregex)",
)
parser.add_argument(
"-iregex",
metavar="PATTERN",
default=r".*\.(?:cpp|cc|c\+\+|cxx|cppm|ccm|cxxm|c\+\+m|c|cl|h|hh|hpp"
r"|hxx|m|mm|inc|js|ts|proto|protodevel|java|cs|json|ipynb|s?vh?)",
help="custom pattern selecting file paths to reformat "
"(case insensitive, overridden by -regex)",
)
parser.add_argument(
"-sort-includes",
action="store_true",
default=False,
help="let clang-format sort include blocks",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="be more verbose, ineffective without -i",
)
parser.add_argument(
"-style",
help="formatting style to apply (LLVM, GNU, Google, Chromium, "
"Microsoft, Mozilla, WebKit)",
)
parser.add_argument(
"-fallback-style",
help="The name of the predefined style used as a"
"fallback in case clang-format is invoked with"
"-style=file, but can not find the .clang-format"
"file to use.",
)
parser.add_argument(
"-binary",
default="clang-format",
help="location of binary to use for clang-format",
)
args = parser.parse_args()
# Extract changed lines for each file.
filename = None
lines_by_file = {}
for line in sys.stdin:
match = re.search(r"^\+\+\+\ (.*?/){%s}(.+)" % args.p, line.rstrip())
if match:
filename = match.group(2)
if filename is None:
continue
if args.regex is not None:
if not re.match("^%s$" % args.regex, filename):
continue
else:
if not re.match("^%s$" % args.iregex, filename, re.IGNORECASE):
continue
match = re.search(r"^@@.*\+(\d+)(?:,(\d+))?", line)
if match:
start_line = int(match.group(1))
line_count = 1
if match.group(2):
line_count = int(match.group(2))
# The input is something like
#
# @@ -1, +0,0 @@
#
# which means no lines were added.
if line_count == 0:
continue
# Also format lines range if line_count is 0 in case of deleting
# surrounding statements.
end_line = start_line
if line_count != 0:
end_line += line_count - 1
lines_by_file.setdefault(filename, []).extend(
["--lines", str(start_line) + ":" + str(end_line)]
)
# Reformat files containing changes in place.
has_diff = False
for filename, lines in lines_by_file.items():
if args.i and args.verbose:
print("Formatting {}".format(filename))
command = [args.binary, filename]
if args.i:
command.append("-i")
if args.sort_includes:
command.append("--sort-includes")
command.extend(lines)
if args.style:
command.extend(["--style", args.style])
if args.fallback_style:
command.extend(["--fallback-style", args.fallback_style])
try:
p = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=None,
stdin=subprocess.PIPE,
universal_newlines=True,
)
except OSError as e:
# Give the user more context when clang-format isn't
# found/isn't executable, etc.
raise RuntimeError(
'Failed to run "%s" - %s"' % (" ".join(command), e.strerror)
)
stdout, _stderr = p.communicate()
if p.returncode != 0:
return p.returncode
if not args.i:
with open(filename) as f:
code = f.readlines()
formatted_code = StringIO(stdout).readlines()
diff = difflib.unified_diff(
code,
formatted_code,
filename,
filename,
"(before formatting)",
"(after formatting)",
)
diff_string = "".join(diff)
if len(diff_string) > 0:
has_diff = True
sys.stdout.write(diff_string)
if has_diff:
return 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,10 @@
import fs from "node:fs/promises";
import initAsync from "./clang-format.js";
const wasm = new URL("./clang-format.wasm", import.meta.url);
export default function (init = fs.readFile(wasm)) {
return initAsync(init);
}
export * from "./clang-format.js";

View File

@@ -0,0 +1,8 @@
import initAsync from "./clang-format.js";
import wasm from "./clang-format.wasm?url";
export default function (input = wasm) {
return initAsync(input);
}
export * from "./clang-format.js";

View File

@@ -0,0 +1,175 @@
export type InitInput =
| RequestInfo
| URL
| Response
| BufferSource
| WebAssembly.Module;
export default function init(input?: InitInput): Promise<void>;
/**
* The style to use for formatting.
* Supported style values are:
* - `LLVM` - A style complying with the LLVM coding standards.
* - `Google` - A style complying with Googles C++ style guide.
* - `Chromium` - A style complying with Chromiums style guide.
* - `Mozilla` - A style complying with Mozillas style guide.
* - `WebKit` - A style complying with WebKits style guide.
* - `Microsoft` - A style complying with Microsofts style guide.
* - `GNU` - A style complying with the GNU coding standards.
* - A string starting with `{`, for example: `{BasedOnStyle: Chromium, IndentWidth: 4, ...}`.
* - A string which represents `.clang-format` content.
*
*/
export type Style =
| "LLVM"
| "Google"
| "Chromium"
| "Mozilla"
| "WebKit"
| "Microsoft"
| "GNU"
| (string & {});
/**
* The filename to use for determining the language.
*/
export type Filename =
| "main.c"
| "main.cc"
| "main.cxx"
| "main.cpp"
| "main.java"
| "main.js"
| "main.mjs"
| "main.ts"
| "main.json"
| "main.m"
| "main.mm"
| "main.proto"
| "main.cs"
| (string & {});
/**
* Formats the given content using the specified style.
*
* @param {string} content - The content to format.
* @param {Filename} filename - The filename to use for determining the language.
* @param {Style} style - The style to use for formatting.
* Supported style values are:
* - `LLVM` - A style complying with the LLVM coding standards.
* - `Google` - A style complying with Googles C++ style guide.
* - `Chromium` - A style complying with Chromiums style guide.
* - `Mozilla` - A style complying with Mozillas style guide.
* - `WebKit` - A style complying with WebKits style guide.
* - `Microsoft` - A style complying with Microsofts style guide.
* - `GNU` - A style complying with the GNU coding standards.
* - A string starting with `{`, for example: `{BasedOnStyle: Chromium, IndentWidth: 4, ...}`.
* - A string which represents `.clang-format` content.
*
* @returns {string} The formatted content.
* @throws {Error}
*
* @see {@link https://clang.llvm.org/docs/ClangFormatStyleOptions.html}
*/
export declare function format(
content: string,
filename?: Filename,
style?: Style,
): string;
/**
* Both the startLine and endLine are 1-based.
*/
export type LineRange = [startLine: number, endLine: number];
/**
* Both the offset and length are measured in bytes.
*/
export type ByteRange = [offset: number, length: number];
/**
* Formats the specified range of lines in the given content using the specified style.
*
* @param {string} content - The content to format.
* @param {LineRange[]} range - Array<[startLine, endLine]> - The range of lines to format.
* Both startLine and endLine are 1-based.
* Multiple ranges can be formatted by specifying several lines arguments.
* @param {Filename} filename - The filename to use for determining the language.
* @param {Style} style - The style to use for formatting.
* Supported style values are:
* - `LLVM` - A style complying with the LLVM coding standards.
* - `Google` - A style complying with Googles C++ style guide.
* - `Chromium` - A style complying with Chromiums style guide.
* - `Mozilla` - A style complying with Mozillas style guide.
* - `WebKit` - A style complying with WebKits style guide.
* - `Microsoft` - A style complying with Microsofts style guide.
* - `GNU` - A style complying with the GNU coding standards.
* - A string starting with `{`, for example: `{BasedOnStyle: Chromium, IndentWidth: 4, ...}`.
* - A string which represents `.clang-format` content.
*
* @returns {string} The formatted content.
* @throws {Error}
*
* @see {@link https://clang.llvm.org/docs/ClangFormatStyleOptions.html}
*/
export declare function format_line_range(
content: string,
range: ByteRange[] | [[offset: number]],
filename?: Filename,
style?: Style,
): string;
/**
* @deprecated Use `format_line_range` instead.
*/
export declare function formatLineRange(
content: string,
range: ByteRange[] | [[offset: number]],
filename?: Filename,
style?: Style,
): string;
/**
* Formats the specified range of bytes in the given content using the specified style.
*
* @param {string} content - The content to format.
* @param {ByteRange[]} range - Array<[offset, length]> - The range of bytes to format.
* @param {Filename} filename - The filename to use for determining the language.
* @param {Style} style - The style to use for formatting.
* Supported style values are:
* - `LLVM` - A style complying with the LLVM coding standards.
* - `Google` - A style complying with Googles C++ style guide.
* - `Chromium` - A style complying with Chromiums style guide.
* - `Mozilla` - A style complying with Mozillas style guide.
* - `WebKit` - A style complying with WebKits style guide.
* - `Microsoft` - A style complying with Microsofts style guide.
* - `GNU` - A style complying with the GNU coding standards.
* - A string starting with `{`, for example: `{BasedOnStyle: Chromium, IndentWidth: 4, ...}`.
* - A string which represents `.clang-format` content.
*
* @returns {string} The formatted content.
* @throws {Error}
*
* @see {@link https://clang.llvm.org/docs/ClangFormatStyleOptions.html}
*/
export declare function format_byte_range(
content: string,
range: LineRange[],
filename?: Filename,
style?: Style,
): string;
/**
* @deprecated Use `format_byte_range` instead.
*/
export declare function formatByteRange(
content: string,
range: LineRange[],
filename?: Filename,
style?: Style,
): string;
export declare function version(): string;
export declare function set_fallback_style(style: Style): void;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
Module.preRun = function customPreRun() {
ENV.PWD = process.cwd();
}

View File

@@ -0,0 +1,748 @@
//===-- clang-format/ClangFormat.cpp - Clang format tool ------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file implements a clang-format tool that automatically formats
/// (fragments of) C++ code.
///
//===----------------------------------------------------------------------===//
#include "clang/../../lib/Format/MatchFilePath.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/Version.h"
#include "clang/Format/Format.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/Process.h"
#include <fstream>
#include "CustomFileSystem.h"
using namespace llvm;
using clang::tooling::Replacements;
static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
// Mark all our options with this category, everything else (except for -version
// and -help) will be hidden.
static cl::OptionCategory ClangFormatCategory("Clang-format options");
static cl::list<unsigned>
Offsets("offset",
cl::desc("Format a range starting at this byte offset.\n"
"Multiple ranges can be formatted by specifying\n"
"several -offset and -length pairs.\n"
"Can only be used with one input file."),
cl::cat(ClangFormatCategory));
static cl::list<unsigned>
Lengths("length",
cl::desc("Format a range of this length (in bytes).\n"
"Multiple ranges can be formatted by specifying\n"
"several -offset and -length pairs.\n"
"When only a single -offset is specified without\n"
"-length, clang-format will format up to the end\n"
"of the file.\n"
"Can only be used with one input file."),
cl::cat(ClangFormatCategory));
static cl::list<std::string>
LineRanges("lines",
cl::desc("<start line>:<end line> - format a range of\n"
"lines (both 1-based).\n"
"Multiple ranges can be formatted by specifying\n"
"several -lines arguments.\n"
"Can't be used with -offset and -length.\n"
"Can only be used with one input file."),
cl::cat(ClangFormatCategory));
static cl::opt<std::string>
Style("style", cl::desc(clang::format::StyleOptionHelpDescription),
cl::init(clang::format::DefaultFormatStyle),
cl::cat(ClangFormatCategory));
static cl::opt<std::string>
FallbackStyle("fallback-style",
cl::desc("The name of the predefined style used as a\n"
"fallback in case clang-format is invoked with\n"
"-style=file, but can not find the .clang-format\n"
"file to use. Defaults to 'LLVM'.\n"
"Use -fallback-style=none to skip formatting."),
cl::init(clang::format::DefaultFallbackStyle),
cl::cat(ClangFormatCategory));
static cl::opt<std::string> AssumeFileName(
"assume-filename",
cl::desc("Set filename used to determine the language and to find\n"
".clang-format file.\n"
"Only used when reading from stdin.\n"
"If this is not passed, the .clang-format file is searched\n"
"relative to the current working directory when reading stdin.\n"
"Unrecognized filenames are treated as C++.\n"
"supported:\n"
" CSharp: .cs\n"
" Java: .java\n"
" JavaScript: .js .mjs .cjs .ts\n"
" Json: .json .ipynb\n"
" Objective-C: .m .mm\n"
" Proto: .proto .protodevel\n"
" TableGen: .td\n"
" TextProto: .txtpb .textpb .pb.txt .textproto .asciipb\n"
" Verilog: .sv .svh .v .vh"),
cl::init("<stdin>"), cl::cat(ClangFormatCategory));
static cl::opt<bool> Inplace("i",
cl::desc("Inplace edit <file>s, if specified."),
cl::cat(ClangFormatCategory));
static cl::opt<bool> OutputXML("output-replacements-xml",
cl::desc("Output replacements as XML."),
cl::cat(ClangFormatCategory));
static cl::opt<bool>
DumpConfig("dump-config",
cl::desc("Dump configuration options to stdout and exit.\n"
"Can be used with -style option."),
cl::cat(ClangFormatCategory));
static cl::opt<unsigned>
Cursor("cursor",
cl::desc("The position of the cursor when invoking\n"
"clang-format from an editor integration"),
cl::init(0), cl::cat(ClangFormatCategory));
static cl::opt<bool>
SortIncludes("sort-includes",
cl::desc("If set, overrides the include sorting behavior\n"
"determined by the SortIncludes style flag"),
cl::cat(ClangFormatCategory));
static cl::opt<std::string> QualifierAlignment(
"qualifier-alignment",
cl::desc("If set, overrides the qualifier alignment style\n"
"determined by the QualifierAlignment style flag"),
cl::init(""), cl::cat(ClangFormatCategory));
static cl::opt<std::string> Files(
"files",
cl::desc("A file containing a list of files to process, one per line."),
cl::value_desc("filename"), cl::init(""), cl::cat(ClangFormatCategory));
static cl::opt<bool>
Verbose("verbose", cl::desc("If set, shows the list of processed files"),
cl::cat(ClangFormatCategory));
// Use --dry-run to match other LLVM tools when you mean do it but don't
// actually do it
static cl::opt<bool>
DryRun("dry-run",
cl::desc("If set, do not actually make the formatting changes"),
cl::cat(ClangFormatCategory));
// Use -n as a common command as an alias for --dry-run. (git and make use -n)
static cl::alias DryRunShort("n", cl::desc("Alias for --dry-run"),
cl::cat(ClangFormatCategory), cl::aliasopt(DryRun),
cl::NotHidden);
// Emulate being able to turn on/off the warning.
static cl::opt<bool>
WarnFormat("Wclang-format-violations",
cl::desc("Warnings about individual formatting changes needed. "
"Used only with --dry-run or -n"),
cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden);
static cl::opt<bool>
NoWarnFormat("Wno-clang-format-violations",
cl::desc("Do not warn about individual formatting changes "
"needed. Used only with --dry-run or -n"),
cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden);
static cl::opt<unsigned> ErrorLimit(
"ferror-limit",
cl::desc("Set the maximum number of clang-format errors to emit\n"
"before stopping (0 = no limit).\n"
"Used only with --dry-run or -n"),
cl::init(0), cl::cat(ClangFormatCategory));
static cl::opt<bool>
WarningsAsErrors("Werror",
cl::desc("If set, changes formatting warnings to errors"),
cl::cat(ClangFormatCategory));
namespace {
enum class WNoError { Unknown };
}
static cl::bits<WNoError> WNoErrorList(
"Wno-error",
cl::desc("If set, don't error out on the specified warning type."),
cl::values(
clEnumValN(WNoError::Unknown, "unknown",
"If set, unknown format options are only warned about.\n"
"This can be used to enable formatting, even if the\n"
"configuration contains unknown (newer) options.\n"
"Use with caution, as this might lead to dramatically\n"
"differing format depending on an option being\n"
"supported or not.")),
cl::cat(ClangFormatCategory));
static cl::opt<bool>
ShowColors("fcolor-diagnostics",
cl::desc("If set, and on a color-capable terminal controls "
"whether or not to print diagnostics in color"),
cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden);
static cl::opt<bool>
NoShowColors("fno-color-diagnostics",
cl::desc("If set, and on a color-capable terminal controls "
"whether or not to print diagnostics in color"),
cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden);
static cl::list<std::string> FileNames(cl::Positional,
cl::desc("[@<file>] [<file> ...]"),
cl::cat(ClangFormatCategory));
static cl::opt<bool> FailOnIncompleteFormat(
"fail-on-incomplete-format",
cl::desc("If set, fail with exit code 1 on incomplete format."),
cl::init(false), cl::cat(ClangFormatCategory));
static cl::opt<bool> ListIgnored("list-ignored",
cl::desc("List ignored files."),
cl::cat(ClangFormatCategory), cl::Hidden);
namespace clang {
namespace format {
static FileID createInMemoryFile(StringRef FileName, MemoryBufferRef Source,
SourceManager &Sources, FileManager &Files,
llvm::vfs::InMemoryFileSystem *MemFS) {
MemFS->addFileNoOwn(FileName, 0, Source);
auto File = Files.getOptionalFileRef(FileName);
assert(File && "File not added to MemFS?");
return Sources.createFileID(*File, SourceLocation(), SrcMgr::C_User);
}
// Parses <start line>:<end line> input to a pair of line numbers.
// Returns true on error.
static bool parseLineRange(StringRef Input, unsigned &FromLine,
unsigned &ToLine) {
std::pair<StringRef, StringRef> LineRange = Input.split(':');
return LineRange.first.getAsInteger(0, FromLine) ||
LineRange.second.getAsInteger(0, ToLine);
}
static bool fillRanges(MemoryBuffer *Code,
std::vector<tooling::Range> &Ranges) {
IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
new llvm::vfs::InMemoryFileSystem);
FileManager Files(FileSystemOptions(), InMemoryFileSystem);
DiagnosticOptions DiagOpts;
DiagnosticsEngine Diagnostics(
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), DiagOpts);
SourceManager Sources(Diagnostics, Files);
const auto ID = createInMemoryFile("<irrelevant>", *Code, Sources, Files,
InMemoryFileSystem.get());
if (!LineRanges.empty()) {
if (!Offsets.empty() || !Lengths.empty()) {
errs() << "error: cannot use -lines with -offset/-length\n";
return true;
}
for (const auto &LineRange : LineRanges) {
unsigned FromLine, ToLine;
if (parseLineRange(LineRange, FromLine, ToLine)) {
errs() << "error: invalid <start line>:<end line> pair\n";
return true;
}
if (FromLine < 1) {
errs() << "error: start line should be at least 1\n";
return true;
}
if (FromLine > ToLine) {
errs() << "error: start line should not exceed end line\n";
return true;
}
const auto Start = Sources.translateLineCol(ID, FromLine, 1);
const auto End = Sources.translateLineCol(ID, ToLine, UINT_MAX);
if (Start.isInvalid() || End.isInvalid())
return true;
const auto Offset = Sources.getFileOffset(Start);
const auto Length = Sources.getFileOffset(End) - Offset;
Ranges.push_back(tooling::Range(Offset, Length));
}
return false;
}
if (Offsets.empty())
Offsets.push_back(0);
const bool EmptyLengths = Lengths.empty();
unsigned Length = 0;
if (Offsets.size() == 1 && EmptyLengths) {
Length = Sources.getFileOffset(Sources.getLocForEndOfFile(ID)) - Offsets[0];
} else if (Offsets.size() != Lengths.size()) {
errs() << "error: number of -offset and -length arguments must match.\n";
return true;
}
for (unsigned I = 0, E = Offsets.size(), CodeSize = Code->getBufferSize();
I < E; ++I) {
const auto Offset = Offsets[I];
if (Offset >= CodeSize) {
errs() << "error: offset " << Offset << " is outside the file\n";
return true;
}
if (!EmptyLengths)
Length = Lengths[I];
if (Offset + Length > CodeSize) {
errs() << "error: invalid length " << Length << ", offset + length ("
<< Offset + Length << ") is outside the file.\n";
return true;
}
Ranges.push_back(tooling::Range(Offset, Length));
}
return false;
}
static void outputReplacementXML(StringRef Text) {
// FIXME: When we sort includes, we need to make sure the stream is correct
// utf-8.
size_t From = 0;
size_t Index;
while ((Index = Text.find_first_of("\n\r<&", From)) != StringRef::npos) {
outs() << Text.substr(From, Index - From);
switch (Text[Index]) {
case '\n':
outs() << "&#10;";
break;
case '\r':
outs() << "&#13;";
break;
case '<':
outs() << "&lt;";
break;
case '&':
outs() << "&amp;";
break;
default:
llvm_unreachable("Unexpected character encountered!");
}
From = Index + 1;
}
outs() << Text.substr(From);
}
static void outputReplacementsXML(const Replacements &Replaces) {
for (const auto &R : Replaces) {
outs() << "<replacement "
<< "offset='" << R.getOffset() << "' "
<< "length='" << R.getLength() << "'>";
outputReplacementXML(R.getReplacementText());
outs() << "</replacement>\n";
}
}
static bool
emitReplacementWarnings(const Replacements &Replaces, StringRef AssumedFileName,
const std::unique_ptr<llvm::MemoryBuffer> &Code) {
unsigned Errors = 0;
if (WarnFormat && !NoWarnFormat) {
SourceMgr Mgr;
const char *StartBuf = Code->getBufferStart();
Mgr.AddNewSourceBuffer(
MemoryBuffer::getMemBuffer(StartBuf, AssumedFileName), SMLoc());
for (const auto &R : Replaces) {
SMDiagnostic Diag = Mgr.GetMessage(
SMLoc::getFromPointer(StartBuf + R.getOffset()),
WarningsAsErrors ? SourceMgr::DiagKind::DK_Error
: SourceMgr::DiagKind::DK_Warning,
"code should be clang-formatted [-Wclang-format-violations]");
Diag.print(nullptr, llvm::errs(), ShowColors && !NoShowColors);
if (ErrorLimit && ++Errors >= ErrorLimit)
break;
}
}
return WarningsAsErrors;
}
static void outputXML(const Replacements &Replaces,
const Replacements &FormatChanges,
const FormattingAttemptStatus &Status,
const cl::opt<unsigned> &Cursor,
unsigned CursorPosition) {
outs() << "<?xml version='1.0'?>\n<replacements "
"xml:space='preserve' incomplete_format='"
<< (Status.FormatComplete ? "false" : "true") << "'";
if (!Status.FormatComplete)
outs() << " line='" << Status.Line << "'";
outs() << ">\n";
if (Cursor.getNumOccurrences() != 0) {
outs() << "<cursor>" << FormatChanges.getShiftedCodePosition(CursorPosition)
<< "</cursor>\n";
}
outputReplacementsXML(Replaces);
outs() << "</replacements>\n";
}
class ClangFormatDiagConsumer : public DiagnosticConsumer {
virtual void anchor() {}
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info) override {
SmallVector<char, 16> vec;
Info.FormatDiagnostic(vec);
errs() << "clang-format error:" << vec << "\n";
}
};
// Returns true on error.
static bool format(StringRef FileName, bool ErrorOnIncompleteFormat = false) {
const bool IsSTDIN = FileName == "-";
if (!OutputXML && Inplace && IsSTDIN) {
errs() << "error: cannot use -i when reading from stdin.\n";
return true;
}
// On Windows, overwriting a file with an open file mapping doesn't work,
// so read the whole file into memory when formatting in-place.
ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
!OutputXML && Inplace
? MemoryBuffer::getFileAsStream(FileName)
: MemoryBuffer::getFileOrSTDIN(FileName, /*IsText=*/true);
if (std::error_code EC = CodeOrErr.getError()) {
errs() << FileName << ": " << EC.message() << "\n";
return true;
}
std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get());
if (Code->getBufferSize() == 0)
return false; // Empty files are formatted correctly.
StringRef BufStr = Code->getBuffer();
const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr);
if (InvalidBOM) {
errs() << "error: encoding with unsupported byte order mark \""
<< InvalidBOM << "\" detected";
if (!IsSTDIN)
errs() << " in file '" << FileName << "'";
errs() << ".\n";
return true;
}
std::vector<tooling::Range> Ranges;
if (fillRanges(Code.get(), Ranges))
return true;
StringRef AssumedFileName = IsSTDIN ? AssumeFileName : FileName;
if (AssumedFileName.empty()) {
llvm::errs() << "error: empty filenames are not allowed\n";
return true;
}
auto RealFS = vfs::getRealFileSystem();
auto CustomFS = new vfs::CustomFileSystem(RealFS);
IntrusiveRefCntPtr<vfs::FileSystem> CustomFSPtr(CustomFS);
Expected<FormatStyle> FormatStyle =
getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer(),
CustomFSPtr.get(), WNoErrorList.isSet(WNoError::Unknown));
if (!FormatStyle) {
llvm::errs() << toString(FormatStyle.takeError()) << "\n";
return true;
}
StringRef QualifierAlignmentOrder = QualifierAlignment;
FormatStyle->QualifierAlignment =
StringSwitch<FormatStyle::QualifierAlignmentStyle>(
QualifierAlignmentOrder.lower())
.Case("right", FormatStyle::QAS_Right)
.Case("left", FormatStyle::QAS_Left)
.Default(FormatStyle->QualifierAlignment);
if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Left) {
FormatStyle->QualifierOrder = {"const", "volatile", "type"};
} else if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Right) {
FormatStyle->QualifierOrder = {"type", "const", "volatile"};
} else if (QualifierAlignmentOrder.contains("type")) {
FormatStyle->QualifierAlignment = FormatStyle::QAS_Custom;
SmallVector<StringRef> Qualifiers;
QualifierAlignmentOrder.split(Qualifiers, " ", /*MaxSplit=*/-1,
/*KeepEmpty=*/false);
FormatStyle->QualifierOrder = {Qualifiers.begin(), Qualifiers.end()};
}
if (SortIncludes.getNumOccurrences() != 0) {
FormatStyle->SortIncludes = {};
if (SortIncludes)
FormatStyle->SortIncludes.Enabled = true;
}
unsigned CursorPosition = Cursor;
Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges,
AssumedFileName, &CursorPosition);
const bool IsJson = FormatStyle->isJson();
// To format JSON insert a variable to trick the code into thinking its
// JavaScript.
if (IsJson && !FormatStyle->DisableFormat) {
auto Err =
Replaces.add(tooling::Replacement(AssumedFileName, 0, 0, "x = "));
if (Err)
llvm::errs() << "Bad Json variable insertion\n";
}
auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces);
if (!ChangedCode) {
llvm::errs() << toString(ChangedCode.takeError()) << "\n";
return true;
}
// Get new affected ranges after sorting `#includes`.
Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges);
FormattingAttemptStatus Status;
Replacements FormatChanges =
reformat(*FormatStyle, *ChangedCode, Ranges, AssumedFileName, &Status);
Replaces = Replaces.merge(FormatChanges);
if (DryRun) {
return Replaces.size() > (IsJson ? 1u : 0u) &&
emitReplacementWarnings(Replaces, AssumedFileName, Code);
}
if (OutputXML) {
outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition);
} else {
IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
new llvm::vfs::InMemoryFileSystem);
FileManager Files(FileSystemOptions(), InMemoryFileSystem);
DiagnosticOptions DiagOpts;
ClangFormatDiagConsumer IgnoreDiagnostics;
DiagnosticsEngine Diagnostics(
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), DiagOpts,
&IgnoreDiagnostics, false);
SourceManager Sources(Diagnostics, Files);
FileID ID = createInMemoryFile(AssumedFileName, *Code, Sources, Files,
InMemoryFileSystem.get());
Rewriter Rewrite(Sources, LangOptions());
tooling::applyAllReplacements(Replaces, Rewrite);
if (Inplace) {
if (Rewrite.overwriteChangedFiles())
return true;
} else {
if (Cursor.getNumOccurrences() != 0) {
outs() << "{ \"Cursor\": "
<< FormatChanges.getShiftedCodePosition(CursorPosition)
<< ", \"IncompleteFormat\": "
<< (Status.FormatComplete ? "false" : "true");
if (!Status.FormatComplete)
outs() << ", \"Line\": " << Status.Line;
outs() << " }\n";
}
Rewrite.getEditBuffer(ID).write(outs());
}
}
return ErrorOnIncompleteFormat && !Status.FormatComplete;
}
} // namespace format
} // namespace clang
static void PrintVersion(raw_ostream &OS) {
OS << clang::getClangToolFullVersion("clang-format") << '\n';
}
// Dump the configuration.
static int dumpConfig() {
std::unique_ptr<llvm::MemoryBuffer> Code;
// We can't read the code to detect the language if there's no file name.
if (!FileNames.empty()) {
// Read in the code in case the filename alone isn't enough to detect the
// language.
ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
MemoryBuffer::getFileOrSTDIN(FileNames[0], /*IsText=*/true);
if (std::error_code EC = CodeOrErr.getError()) {
llvm::errs() << EC.message() << "\n";
return 1;
}
Code = std::move(CodeOrErr.get());
}
auto RealFS = vfs::getRealFileSystem();
auto CustomFS = new vfs::CustomFileSystem(RealFS);
IntrusiveRefCntPtr<vfs::FileSystem> CustomFSPtr(CustomFS);
Expected<clang::format::FormatStyle> FormatStyle = clang::format::getStyle(
Style,
FileNames.empty() || FileNames[0] == "-" ? AssumeFileName : FileNames[0],
FallbackStyle, Code ? Code->getBuffer() : "", CustomFSPtr.get());
if (!FormatStyle) {
llvm::errs() << toString(FormatStyle.takeError()) << "\n";
return 1;
}
std::string Config = clang::format::configurationAsText(*FormatStyle);
outs() << Config << "\n";
return 0;
}
using String = SmallString<128>;
static String IgnoreDir; // Directory of .clang-format-ignore file.
static String PrevDir; // Directory of previous `FilePath`.
static SmallVector<String> Patterns; // Patterns in .clang-format-ignore file.
// Check whether `FilePath` is ignored according to the nearest
// .clang-format-ignore file based on the rules below:
// - A blank line is skipped.
// - Leading and trailing spaces of a line are trimmed.
// - A line starting with a hash (`#`) is a comment.
// - A non-comment line is a single pattern.
// - The slash (`/`) is used as the directory separator.
// - A pattern is relative to the directory of the .clang-format-ignore file (or
// the root directory if the pattern starts with a slash).
// - A pattern is negated if it starts with a bang (`!`).
static bool isIgnored(StringRef FilePath) {
using namespace llvm::sys::fs;
if (!is_regular_file(FilePath))
return false;
String Path;
String AbsPath{FilePath};
auto PathStyle = vfs::getPathStyle();
using namespace llvm::sys::path;
vfs::make_absolute(AbsPath);
remove_dots(AbsPath, /*remove_dot_dot=*/true, PathStyle);
if (StringRef Dir{parent_path(AbsPath, PathStyle)}; PrevDir != Dir) {
PrevDir = Dir;
for (;;) {
Path = Dir;
append(Path, PathStyle, ".clang-format-ignore");
if (is_regular_file(Path))
break;
Dir = parent_path(Dir, PathStyle);
if (Dir.empty())
return false;
}
IgnoreDir = convert_to_slash(Dir, PathStyle);
std::ifstream IgnoreFile{Path.c_str()};
if (!IgnoreFile.good())
return false;
Patterns.clear();
for (std::string Line; std::getline(IgnoreFile, Line);) {
if (const auto Pattern{StringRef{Line}.trim()};
// Skip empty and comment lines.
!Pattern.empty() && Pattern[0] != '#') {
Patterns.push_back(Pattern);
}
}
}
if (IgnoreDir.empty())
return false;
const auto Pathname{convert_to_slash(AbsPath, PathStyle)};
for (const auto &Pat : Patterns) {
const bool IsNegated = Pat[0] == '!';
StringRef Pattern{Pat};
if (IsNegated)
Pattern = Pattern.drop_front();
if (Pattern.empty())
continue;
Pattern = Pattern.ltrim();
// `Pattern` is relative to `IgnoreDir` unless it starts with a slash.
// This doesn't support patterns containing drive names (e.g. `C:`).
if (Pattern[0] != '/') {
Path = IgnoreDir;
append(Path, Style::posix, Pattern);
remove_dots(Path, /*remove_dot_dot=*/true, Style::posix);
Pattern = Path;
}
if (clang::format::matchFilePath(Pattern, Pathname) == !IsNegated)
return true;
}
return false;
}
int main(int argc, const char **argv) {
InitLLVM X(argc, argv);
cl::HideUnrelatedOptions(ClangFormatCategory);
cl::SetVersionPrinter(PrintVersion);
cl::ParseCommandLineOptions(
argc, argv,
"A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# "
"code.\n\n"
"If no arguments are specified, it formats the code from standard input\n"
"and writes the result to the standard output.\n"
"If <file>s are given, it reformats the files. If -i is specified\n"
"together with <file>s, the files are edited in-place. Otherwise, the\n"
"result is written to the standard output.\n");
if (Help) {
cl::PrintHelpMessage();
return 0;
}
if (DumpConfig)
return dumpConfig();
if (!Files.empty()) {
std::ifstream ExternalFileOfFiles{std::string(Files)};
std::string Line;
unsigned LineNo = 1;
while (std::getline(ExternalFileOfFiles, Line)) {
FileNames.push_back(Line);
LineNo++;
}
errs() << "Clang-formatting " << LineNo << " files\n";
}
if (FileNames.empty()) {
if (isIgnored(AssumeFileName))
return 0;
return clang::format::format("-", FailOnIncompleteFormat);
}
if (FileNames.size() > 1 &&
(!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) {
errs() << "error: -offset, -length and -lines can only be used for "
"single file.\n";
return 1;
}
unsigned FileNo = 1;
bool Error = false;
for (const auto &FileName : FileNames) {
const bool Ignored = isIgnored(FileName);
if (ListIgnored) {
if (Ignored)
outs() << FileName << '\n';
continue;
}
if (Ignored)
continue;
if (Verbose) {
errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] "
<< FileName << "\n";
}
Error |= clang::format::format(FileName, FailOnIncompleteFormat);
}
return Error ? 1 : 0;
}

View File

@@ -0,0 +1,858 @@
#!/usr/bin/env python3
#
# ===- git-clang-format - ClangFormat Git Integration -------*- python -*--=== #
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# ===----------------------------------------------------------------------=== #
r"""
clang-format git integration
============================
This file provides a clang-format integration for git. Put it somewhere in your
path and ensure that it is executable. Then, "git clang-format" will invoke
clang-format on the changes in current files or a specific commit.
For further details, run:
git clang-format -h
Requires Python version >=3.8
"""
from __future__ import absolute_import, division, print_function
import argparse
import collections
import contextlib
import errno
import os
import re
import subprocess
import sys
usage = "git clang-format [OPTIONS] [<commit>] [<commit>|--staged] [--] [<file>...]"
desc = """
If zero or one commits are given, run clang-format on all lines that differ
between the working directory and <commit>, which defaults to HEAD. Changes are
only applied to the working directory, or in the stage/index.
Examples:
To format staged changes, i.e everything that's been `git add`ed:
git clang-format
To also format everything touched in the most recent commit:
git clang-format HEAD~1
If you're on a branch off main, to format everything touched on your branch:
git clang-format main
If two commits are given (requires --diff), run clang-format on all lines in the
second <commit> that differ from the first <commit>.
The following git-config settings set the default of the corresponding option:
clangFormat.binary
clangFormat.commit
clangFormat.extensions
clangFormat.style
"""
# Name of the temporary index file in which save the output of clang-format.
# This file is created within the .git directory.
temp_index_basename = "clang-format-index"
Range = collections.namedtuple("Range", "start, count")
def main():
config = load_git_config()
# In order to keep '--' yet allow options after positionals, we need to
# check for '--' ourselves. (Setting nargs='*' throws away the '--', while
# nargs=argparse.REMAINDER disallows options after positionals.)
argv = sys.argv[1:]
try:
idx = argv.index("--")
except ValueError:
dash_dash = []
else:
dash_dash = argv[idx:]
argv = argv[:idx]
default_extensions = ",".join(
[
# From clang/lib/Frontend/FrontendOptions.cpp, all lower case
"c",
"h", # C
"m", # ObjC
"mm", # ObjC++
"cc",
"cp",
"cpp",
"c++",
"cxx",
"hh",
"hpp",
"hxx",
"inc", # C++
"ccm",
"cppm",
"cxxm",
"c++m", # C++ Modules
"cu",
"cuh", # CUDA
"cl", # OpenCL
# Other languages that clang-format supports
"proto",
"protodevel", # Protocol Buffers
"java", # Java
"js",
"mjs",
"cjs", # JavaScript
"ts", # TypeScript
"cs", # C Sharp
"json",
"ipynb", # Json
"sv",
"svh",
"v",
"vh", # Verilog
"td", # TableGen
"txtpb",
"textpb",
"pb.txt",
"textproto",
"asciipb", # TextProto
]
)
p = argparse.ArgumentParser(
usage=usage,
formatter_class=argparse.RawDescriptionHelpFormatter,
description=desc,
)
p.add_argument(
"--binary",
default=config.get("clangformat.binary", "clang-format"),
help="path to clang-format",
),
p.add_argument(
"--commit",
default=config.get("clangformat.commit", "HEAD"),
help="default commit to use if none is specified",
),
p.add_argument(
"--diff",
action="store_true",
help="print a diff instead of applying the changes",
)
p.add_argument(
"--diffstat",
action="store_true",
help="print a diffstat instead of applying the changes",
)
p.add_argument(
"--extensions",
default=config.get("clangformat.extensions", default_extensions),
help=(
"comma-separated list of file extensions to format, "
"excluding the period and case-insensitive"
),
),
p.add_argument(
"-f",
"--force",
action="store_true",
help="allow changes to unstaged files",
)
p.add_argument(
"-p", "--patch", action="store_true", help="select hunks interactively"
)
p.add_argument(
"-q",
"--quiet",
action="count",
default=0,
help="print less information",
)
p.add_argument(
"--staged",
"--cached",
action="store_true",
help="format lines in the stage instead of the working dir",
)
p.add_argument(
"--style",
default=config.get("clangformat.style", None),
help="passed to clang-format",
),
p.add_argument(
"-v",
"--verbose",
action="count",
default=0,
help="print extra information",
)
p.add_argument(
"--diff_from_common_commit",
action="store_true",
help=(
"diff from the last common commit for commits in "
"separate branches rather than the exact point of the "
"commits"
),
)
# We gather all the remaining positional arguments into 'args' since we need
# to use some heuristics to determine whether or not <commit> was present.
# However, to print pretty messages, we make use of metavar and help.
p.add_argument(
"args",
nargs="*",
metavar="<commit>",
help="revision from which to compute the diff",
)
p.add_argument(
"ignored",
nargs="*",
metavar="<file>...",
help="if specified, only consider differences in these files",
)
opts = p.parse_args(argv)
opts.verbose -= opts.quiet
del opts.quiet
commits, files = interpret_args(opts.args, dash_dash, opts.commit)
if len(commits) > 2:
die("at most two commits allowed; %d given" % len(commits))
if len(commits) == 2:
if opts.staged:
die("--staged is not allowed when two commits are given")
if not opts.diff:
die("--diff is required when two commits are given")
elif opts.diff_from_common_commit:
die("--diff_from_common_commit is only allowed when two commits are given")
if os.path.dirname(opts.binary):
opts.binary = os.path.abspath(opts.binary)
changed_lines = compute_diff_and_extract_lines(
commits, files, opts.staged, opts.diff_from_common_commit
)
if opts.verbose >= 1:
ignored_files = set(changed_lines)
filter_by_extension(changed_lines, opts.extensions.lower().split(","))
# The computed diff outputs absolute paths, so we must cd before accessing
# those files.
cd_to_toplevel()
filter_symlinks(changed_lines)
filter_ignored_files(changed_lines, binary=opts.binary)
if opts.verbose >= 1:
ignored_files.difference_update(changed_lines)
if ignored_files:
print(
"Ignoring the following files (wrong extension, symlink, or "
"ignored by clang-format):"
)
for filename in ignored_files:
print(" %s" % filename)
if changed_lines:
print("Running clang-format on the following files:")
for filename in changed_lines:
print(" %s" % filename)
if not changed_lines:
if opts.verbose >= 0:
print("no modified files to format")
return 0
if len(commits) > 1:
old_tree = commits[1]
revision = old_tree
elif opts.staged:
old_tree = create_tree_from_index(changed_lines)
revision = ""
else:
old_tree = create_tree_from_workdir(changed_lines)
revision = None
new_tree = run_clang_format_and_save_to_tree(
changed_lines, revision, binary=opts.binary, style=opts.style
)
if opts.verbose >= 1:
print("old tree: %s" % old_tree)
print("new tree: %s" % new_tree)
if old_tree == new_tree:
if opts.verbose >= 0:
print("clang-format did not modify any files")
return 0
if opts.diff:
return print_diff(old_tree, new_tree)
if opts.diffstat:
return print_diffstat(old_tree, new_tree)
changed_files = apply_changes(
old_tree, new_tree, force=opts.force, patch_mode=opts.patch
)
if (opts.verbose >= 0 and not opts.patch) or opts.verbose >= 1:
print("changed files:")
for filename in changed_files:
print(" %s" % filename)
return 1
def load_git_config(non_string_options=None):
"""Return the git configuration as a dictionary.
All options are assumed to be strings unless in `non_string_options`, in
which is a dictionary mapping option name (in lower case) to either "--bool"
or "--int"."""
if non_string_options is None:
non_string_options = {}
out = {}
for entry in run("git", "config", "--list", "--null").split("\0"):
if entry:
if "\n" in entry:
name, value = entry.split("\n", 1)
else:
# A setting with no '=' ('\n' with --null) is implicitly 'true'
name = entry
value = "true"
if name in non_string_options:
value = run("git", "config", non_string_options[name], name)
out[name] = value
return out
def interpret_args(args, dash_dash, default_commit):
"""Interpret `args` as "[commits] [--] [files]" and return (commits, files).
It is assumed that "--" and everything that follows has been removed from
args and placed in `dash_dash`.
If "--" is present (i.e., `dash_dash` is non-empty), the arguments to its
left (if present) are taken as commits. Otherwise, the arguments are
checked from left to right if they are commits or files. If commits are not
given, a list with `default_commit` is used."""
if dash_dash:
if len(args) == 0:
commits = [default_commit]
else:
commits = args
for commit in commits:
object_type = get_object_type(commit)
if object_type not in ("commit", "tag"):
if object_type is None:
die("'%s' is not a commit" % commit)
else:
die(
"'%s' is a %s, but a commit was expected"
% (commit, object_type)
)
files = dash_dash[1:]
elif args:
commits = []
while args:
if not disambiguate_revision(args[0]):
break
commits.append(args.pop(0))
if not commits:
commits = [default_commit]
files = args
else:
commits = [default_commit]
files = []
return commits, files
def disambiguate_revision(value):
"""Returns True if `value` is a revision, False if it is a file, or dies."""
# If `value` is ambiguous (neither a commit nor a file), the following
# command will die with an appropriate error message.
run("git", "rev-parse", value, verbose=False)
object_type = get_object_type(value)
if object_type is None:
return False
if object_type in ("commit", "tag"):
return True
die("`%s` is a %s, but a commit or filename was expected" % (value, object_type))
def get_object_type(value):
"""Returns a string description of an object's type, or None if it is not
a valid git object."""
cmd = ["git", "cat-file", "-t", value]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
if p.returncode != 0:
return None
return convert_string(stdout.strip())
def compute_diff_and_extract_lines(commits, files, staged, diff_common_commit):
"""Calls compute_diff() followed by extract_lines()."""
diff_process = compute_diff(commits, files, staged, diff_common_commit)
changed_lines = extract_lines(diff_process.stdout)
diff_process.stdout.close()
diff_process.wait()
if diff_process.returncode != 0:
# Assume error was already printed to stderr.
sys.exit(2)
return changed_lines
def compute_diff(commits, files, staged, diff_common_commit):
"""Return a subprocess object producing the diff from `commits`.
The return value's `stdin` file object will produce a patch with the
differences between the working directory (or stage if --staged is used) and
the first commit if a single one was specified, or the difference between
both specified commits, filtered on `files` (if non-empty).
Zero context lines are used in the patch."""
git_tool = "diff-index"
extra_args = []
if len(commits) == 2:
git_tool = "diff-tree"
if diff_common_commit:
commits = [f"{commits[0]}...{commits[1]}"]
elif staged:
extra_args += ["--cached"]
cmd = ["git", git_tool, "-p", "-U0"] + extra_args + commits + ["--"]
cmd.extend(files)
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
p.stdin.close()
return p
def extract_lines(patch_file):
"""Extract the changed lines in `patch_file`.
The return value is a dictionary mapping filename to a list of (start_line,
line_count) pairs.
The input must have been produced with ``-U0``, meaning unidiff format with
zero lines of context. The return value is a dict mapping filename to a
list of line `Range`s."""
matches = {}
for line in patch_file:
line = convert_string(line)
match = re.search(r"^\+\+\+\ [^/]+/(.*)", line)
if match:
filename = match.group(1).rstrip("\r\n\t")
match = re.search(r"^@@ -[0-9,]+ \+(\d+)(,(\d+))?", line)
if match:
start_line = int(match.group(1))
line_count = 1
if match.group(3):
line_count = int(match.group(3))
if line_count == 0:
line_count = 1
if start_line == 0:
continue
matches.setdefault(filename, []).append(Range(start_line, line_count))
return matches
def filter_by_extension(dictionary, allowed_extensions):
"""Delete every key in `dictionary` that doesn't have an allowed extension.
`allowed_extensions` must be a collection of lowercase file extensions,
excluding the period."""
allowed_extensions = frozenset(allowed_extensions)
for filename in list(dictionary.keys()):
base_ext = filename.rsplit(".", 1)
if len(base_ext) == 1 and "" in allowed_extensions:
continue
if len(base_ext) == 1 or base_ext[1].lower() not in allowed_extensions:
del dictionary[filename]
def filter_symlinks(dictionary):
"""Delete every key in `dictionary` that is a symlink."""
for filename in list(dictionary.keys()):
if os.path.islink(filename):
del dictionary[filename]
def filter_ignored_files(dictionary, binary):
"""Delete every key in `dictionary` that is ignored by clang-format."""
ignored_files = run(binary, "-list-ignored", *dictionary.keys())
if not ignored_files:
return
ignored_files = ignored_files.split("\n")
for filename in ignored_files:
del dictionary[filename]
def cd_to_toplevel():
"""Change to the top level of the git repository."""
toplevel = run("git", "rev-parse", "--show-toplevel")
os.chdir(toplevel)
def create_tree_from_workdir(filenames):
"""Create a new git tree with the given files from the working directory.
Returns the object ID (SHA-1) of the created tree."""
return create_tree(filenames, "--stdin")
def create_tree_from_index(filenames):
# Copy the environment, because the files have to be read from the original
# index.
env = os.environ.copy()
def index_contents_generator():
for filename in filenames:
git_ls_files_cmd = [
"git",
"ls-files",
"--stage",
"-z",
"--",
filename,
]
git_ls_files = subprocess.Popen(
git_ls_files_cmd,
env=env,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
)
stdout = git_ls_files.communicate()[0]
yield convert_string(stdout.split(b"\0")[0])
return create_tree(index_contents_generator(), "--index-info")
def run_clang_format_and_save_to_tree(
changed_lines, revision=None, binary="clang-format", style=None
):
"""Run clang-format on each file and save the result to a git tree.
Returns the object ID (SHA-1) of the created tree."""
# Copy the environment when formatting the files in the index, because the
# files have to be read from the original index.
env = os.environ.copy() if revision == "" else None
def iteritems(container):
try:
return container.iteritems() # Python 2
except AttributeError:
return container.items() # Python 3
def index_info_generator():
for filename, line_ranges in iteritems(changed_lines):
if revision is not None:
if len(revision) > 0:
git_metadata_cmd = [
"git",
"ls-tree",
"%s:%s" % (revision, os.path.dirname(filename)),
os.path.basename(filename),
]
else:
git_metadata_cmd = [
"git",
"ls-files",
"--stage",
"--",
filename,
]
git_metadata = subprocess.Popen(
git_metadata_cmd,
env=env,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
)
stdout = git_metadata.communicate()[0]
mode = oct(int(stdout.split()[0], 8))
else:
mode = oct(os.stat(filename).st_mode)
# Adjust python3 octal format so that it matches what git expects
if mode.startswith("0o"):
mode = "0" + mode[2:]
blob_id = clang_format_to_blob(
filename,
line_ranges,
revision=revision,
binary=binary,
style=style,
env=env,
)
yield "%s %s\t%s" % (mode, blob_id, filename)
return create_tree(index_info_generator(), "--index-info")
def create_tree(input_lines, mode):
"""Create a tree object from the given input.
If mode is '--stdin', it must be a list of filenames. If mode is
'--index-info' is must be a list of values suitable for "git update-index
--index-info", such as "<mode> <SP> <sha1> <TAB> <filename>". Any other
mode is invalid."""
assert mode in ("--stdin", "--index-info")
cmd = ["git", "update-index", "--add", "-z", mode]
with temporary_index_file():
p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
for line in input_lines:
p.stdin.write(to_bytes("%s\0" % line))
p.stdin.close()
if p.wait() != 0:
die("`%s` failed" % " ".join(cmd))
tree_id = run("git", "write-tree")
return tree_id
def clang_format_to_blob(
filename,
line_ranges,
revision=None,
binary="clang-format",
style=None,
env=None,
):
"""Run clang-format on the given file and save the result to a git blob.
Runs on the file in `revision` if not None, or on the file in the working
directory if `revision` is None. Revision can be set to an empty string to
run clang-format on the file in the index.
Returns the object ID (SHA-1) of the created blob."""
clang_format_cmd = [binary]
if style:
clang_format_cmd.extend(["--style=" + style])
clang_format_cmd.extend(
[
"--lines=%s:%s" % (start_line, start_line + line_count - 1)
for start_line, line_count in line_ranges
]
)
if revision is not None:
clang_format_cmd.extend(["--assume-filename=" + filename])
git_show_cmd = [
"git",
"cat-file",
"blob",
"%s:%s" % (revision, filename),
]
git_show = subprocess.Popen(
git_show_cmd, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE
)
git_show.stdin.close()
clang_format_stdin = git_show.stdout
else:
clang_format_cmd.extend([filename])
git_show = None
clang_format_stdin = subprocess.PIPE
try:
clang_format = subprocess.Popen(
clang_format_cmd, stdin=clang_format_stdin, stdout=subprocess.PIPE
)
if clang_format_stdin == subprocess.PIPE:
clang_format_stdin = clang_format.stdin
except OSError as e:
if e.errno == errno.ENOENT:
die('cannot find executable "%s"' % binary)
else:
raise
clang_format_stdin.close()
hash_object_cmd = [
"git",
"hash-object",
"-w",
"--path=" + filename,
"--stdin",
]
hash_object = subprocess.Popen(
hash_object_cmd, stdin=clang_format.stdout, stdout=subprocess.PIPE
)
clang_format.stdout.close()
stdout = hash_object.communicate()[0]
if hash_object.returncode != 0:
die("`%s` failed" % " ".join(hash_object_cmd))
if clang_format.wait() != 0:
die("`%s` failed" % " ".join(clang_format_cmd))
if git_show and git_show.wait() != 0:
die("`%s` failed" % " ".join(git_show_cmd))
return convert_string(stdout).rstrip("\r\n")
@contextlib.contextmanager
def temporary_index_file(tree=None):
"""Context manager for setting GIT_INDEX_FILE to a temporary file and
deleting the file afterward."""
index_path = create_temporary_index(tree)
old_index_path = os.environ.get("GIT_INDEX_FILE")
os.environ["GIT_INDEX_FILE"] = index_path
try:
yield
finally:
if old_index_path is None:
del os.environ["GIT_INDEX_FILE"]
else:
os.environ["GIT_INDEX_FILE"] = old_index_path
os.remove(index_path)
def create_temporary_index(tree=None):
"""Create a temporary index file and return the created file's path.
If `tree` is not None, use that as the tree to read in. Otherwise, an
empty index is created."""
gitdir = run("git", "rev-parse", "--git-dir")
path = os.path.join(gitdir, temp_index_basename)
if tree is None:
tree = "--empty"
run("git", "read-tree", "--index-output=" + path, tree)
return path
def print_diff(old_tree, new_tree):
"""Print the diff between the two trees to stdout."""
# We use the porcelain 'diff' and not plumbing 'diff-tree' because the
# output is expected to be viewed by the user, and only the former does nice
# things like color and pagination.
#
# We also only print modified files since `new_tree` only contains the files
# that were modified, so unmodified files would show as deleted without the
# filter.
return subprocess.run(
["git", "diff", "--diff-filter=M", "--exit-code", old_tree, new_tree]
).returncode
def print_diffstat(old_tree, new_tree):
"""Print the diffstat between the two trees to stdout."""
# We use the porcelain 'diff' and not plumbing 'diff-tree' because the
# output is expected to be viewed by the user, and only the former does nice
# things like color and pagination.
#
# We also only print modified files since `new_tree` only contains the files
# that were modified, so unmodified files would show as deleted without the
# filter.
return subprocess.run(
[
"git",
"diff",
"--diff-filter=M",
"--exit-code",
"--stat",
old_tree,
new_tree,
]
).returncode
def apply_changes(old_tree, new_tree, force=False, patch_mode=False):
"""Apply the changes in `new_tree` to the working directory.
Bails if there are local changes in those files and not `force`. If
`patch_mode`, runs `git checkout --patch` to select hunks interactively."""
changed_files = (
run(
"git",
"diff-tree",
"--diff-filter=M",
"-r",
"-z",
"--name-only",
old_tree,
new_tree,
)
.rstrip("\0")
.split("\0")
)
if not force:
unstaged_files = run("git", "diff-files", "--name-status", *changed_files)
if unstaged_files:
print(
"The following files would be modified but have unstaged changes:",
file=sys.stderr,
)
print(unstaged_files, file=sys.stderr)
print("Please commit, stage, or stash them first.", file=sys.stderr)
sys.exit(2)
if patch_mode:
# In patch mode, we could just as well create an index from the new tree
# and checkout from that, but then the user will be presented with a
# message saying "Discard ... from worktree". Instead, we use the old
# tree as the index and checkout from new_tree, which gives the slightly
# better message, "Apply ... to index and worktree". This is not quite
# right, since it won't be applied to the user's index, but oh well.
with temporary_index_file(old_tree):
subprocess.run(["git", "checkout", "--patch", new_tree], check=True)
index_tree = old_tree
else:
with temporary_index_file(new_tree):
run("git", "checkout-index", "-f", "--", *changed_files)
return changed_files
def run(*args, **kwargs):
stdin = kwargs.pop("stdin", "")
verbose = kwargs.pop("verbose", True)
strip = kwargs.pop("strip", True)
for name in kwargs:
raise TypeError("run() got an unexpected keyword argument '%s'" % name)
p = subprocess.Popen(
args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
)
stdout, stderr = p.communicate(input=stdin)
stdout = convert_string(stdout)
stderr = convert_string(stderr)
if p.returncode == 0:
if stderr:
if verbose:
print("`%s` printed to stderr:" % " ".join(args), file=sys.stderr)
print(stderr.rstrip(), file=sys.stderr)
if strip:
stdout = stdout.rstrip("\r\n")
return stdout
if verbose:
print("`%s` returned %s" % (" ".join(args), p.returncode), file=sys.stderr)
if stderr:
print(stderr.rstrip(), file=sys.stderr)
sys.exit(2)
def die(message):
print("error:", message, file=sys.stderr)
sys.exit(2)
def to_bytes(str_input):
# Encode to UTF-8 to get binary data.
if isinstance(str_input, bytes):
return str_input
return str_input.encode("utf-8")
def to_string(bytes_input):
if isinstance(bytes_input, str):
return bytes_input
return bytes_input.encode("utf-8")
def convert_string(bytes_input):
try:
return to_string(bytes_input.decode("utf-8"))
except AttributeError: # 'str' object has no attribute 'decode'.
return str(bytes_input)
except UnicodeError:
return str(bytes_input)
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,155 @@
/**
* Prettier Plugin for C/C++ formatting using clang-format WebAssembly
*
* This plugin provides support for formatting C/C++ files using the clang-format WASM implementation.
* It supports various C/C++ file extensions and common clang-format styles.
*/
import type { Plugin, Parser, Printer } from 'prettier';
// Import the clang-format WASM module
import clangFormatInit, { format } from './clang-format-vite.js';
const parserName = 'clang';
// Language configuration
const languages = [
{
name: 'C',
aliases: ['c'],
parsers: [parserName],
extensions: ['.c', '.h'],
aceMode: 'c_cpp',
tmScope: 'source.c',
linguistLanguageId: 50,
vscodeLanguageIds: ['c']
},
{
name: 'C++',
aliases: ['cpp', 'cxx', 'cc'],
parsers: [parserName],
extensions: ['.cpp', '.cxx', '.cc', '.hpp', '.hxx', '.hh', '.C', '.H'],
aceMode: 'c_cpp',
tmScope: 'source.cpp',
linguistLanguageId: 43,
vscodeLanguageIds: ['cpp']
},
{
name: 'Objective-C',
aliases: ['objc', 'objectivec'],
parsers: [parserName],
extensions: ['.m', '.mm'],
aceMode: 'objectivec',
tmScope: 'source.objc',
linguistLanguageId: 259,
vscodeLanguageIds: ['objective-c']
}
];
// Parser configuration
const clangParser: Parser<string> = {
astFormat: parserName,
parse: (text: string) => text,
locStart: () => 0,
locEnd: (node: string) => node.length,
};
// Initialize clang-format WASM module
let initPromise: Promise<void> | null = null;
let isInitialized = false;
function initClangFormat(): Promise<void> {
if (initPromise) {
return initPromise;
}
initPromise = (async () => {
if (!isInitialized) {
await clangFormatInit();
isInitialized = true;
}
})();
return initPromise;
}
// Printer configuration
const clangPrinter: Printer<string> = {
print: (path, options) => {
try {
if (!isInitialized) {
console.warn('clang-format WASM module not initialized, returning original text');
return (path as any).getValue ? (path as any).getValue() : path.node;
}
const text = (path as any).getValue ? (path as any).getValue() : path.node;
const style = getClangStyle(options);
// Format using clang-format (synchronous call)
const formatted = format(text, undefined, style);
return formatted.trim();
} catch (error) {
console.warn('clang-format failed:', error);
// Return original text if formatting fails
return (path as any).getValue ? (path as any).getValue() : path.node;
}
},
};
// Helper function to determine clang-format style
function getClangStyle(options: any): string {
// You can extend this to support more options
const style = options.clangStyle || 'LLVM';
// Support common styles
const validStyles = ['LLVM', 'Google', 'Chromium', 'Mozilla', 'WebKit', 'Microsoft', 'GNU'];
if (validStyles.includes(style)) {
return style;
}
// Default to LLVM style
return 'LLVM';
}
// Plugin options
const options = {
clangStyle: {
since: '0.0.1',
category: 'Format' as const,
type: 'choice' as const,
default: 'LLVM',
description: 'The clang-format style to use',
choices: [
{ value: 'LLVM', description: 'LLVM coding standards' },
{ value: 'Google', description: "Google's C++ style guide" },
{ value: 'Chromium', description: "Chromium's style guide" },
{ value: 'Mozilla', description: "Mozilla's style guide" },
{ value: 'WebKit', description: "WebKit's style guide" },
{ value: 'Microsoft', description: "Microsoft's style guide" },
{ value: 'GNU', description: 'GNU coding standards' }
]
}
};
// Plugin object
const clangPlugin: Plugin = {
languages,
parsers: {
[parserName]: clangParser,
},
printers: {
[parserName]: clangPrinter,
},
options,
};
// Initialize WASM module when plugin loads
initClangFormat().catch(error => {
console.warn('Failed to initialize clang-format WASM module:', error);
});
export default clangPlugin;
export { languages };
export const parsers = clangPlugin.parsers;
export const printers = clangPlugin.printers;

View File

@@ -0,0 +1,323 @@
//===-- clang-format/ClangFormat.cpp - Clang format tool ------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file implements a clang-format tool that automatically formats
/// (fragments of) C++ code.
///
//===----------------------------------------------------------------------===//
#include "lib.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/Version.h"
#include "clang/Format/Format.h"
#include "clang/Rewrite/Core/Rewriter.h"
using namespace llvm;
using clang::tooling::Replacements;
static std::string FallbackStyle{clang::format::DefaultFallbackStyle};
static unsigned Cursor{0};
static bool SortIncludes{false};
static std::string QualifierAlignment{""};
static auto Ok(const std::string content) -> Result {
return {false, std::move(content)};
}
static auto Err(const std::string content) -> Result {
return {true, std::move(content)};
}
namespace clang {
namespace format {
static FileID createInMemoryFile(StringRef FileName, MemoryBufferRef Source,
SourceManager &Sources, FileManager &Files,
llvm::vfs::InMemoryFileSystem *MemFS) {
MemFS->addFileNoOwn(FileName, 0, Source);
auto File = Files.getOptionalFileRef(FileName);
assert(File && "File not added to MemFS?");
return Sources.createFileID(*File, SourceLocation(), SrcMgr::C_User);
}
static auto fillRanges(MemoryBuffer *Code, std::vector<tooling::Range> &Ranges)
-> void {
Ranges.push_back(tooling::Range(0, Code->getBuffer().size()));
}
static auto isPredefinedStyle(StringRef style) -> bool {
return StringSwitch<bool>(style.lower())
.Cases("llvm", "chromium", "mozilla", "google", "webkit", "gnu",
"microsoft", "none", "file", true)
.Default(false);
}
static auto format_range(const std::unique_ptr<llvm::MemoryBuffer> code,
const std::string assumedFileName,
const std::string style,
std::vector<tooling::Range> ranges) -> Result {
StringRef BufStr = code->getBuffer();
const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr);
if (InvalidBOM) {
std::stringstream err;
err << "encoding with unsupported byte order mark \"" << InvalidBOM
<< "\" detected.";
return Err(err.str());
}
StringRef AssumedFileName = assumedFileName;
if (AssumedFileName.empty())
AssumedFileName = "<stdin>";
IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
new llvm::vfs::InMemoryFileSystem);
FileManager Files(FileSystemOptions(), InMemoryFileSystem);
DiagnosticOptions DiagOpts;
DiagnosticsEngine Diagnostics(
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), DiagOpts);
SourceManager Sources(Diagnostics, Files);
StringRef _style = style;
if (!_style.starts_with("{") && !isPredefinedStyle(_style)) {
std::unique_ptr<llvm::MemoryBuffer> DotClangFormat =
MemoryBuffer::getMemBuffer(style);
createInMemoryFile(".clang-format", *DotClangFormat.get(), Sources, Files,
InMemoryFileSystem.get());
_style = "file:.clang-format";
}
llvm::Expected<FormatStyle> FormatStyle =
getStyle(_style, AssumedFileName, FallbackStyle, code->getBuffer(),
InMemoryFileSystem.get(), false);
InMemoryFileSystem.reset();
if (!FormatStyle) {
std::string err = llvm::toString(FormatStyle.takeError());
return Err(err);
}
StringRef QualifierAlignmentOrder = QualifierAlignment;
FormatStyle->QualifierAlignment =
StringSwitch<FormatStyle::QualifierAlignmentStyle>(
QualifierAlignmentOrder.lower())
.Case("right", FormatStyle::QAS_Right)
.Case("left", FormatStyle::QAS_Left)
.Default(FormatStyle->QualifierAlignment);
if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Left) {
FormatStyle->QualifierOrder = {"const", "volatile", "type"};
} else if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Right) {
FormatStyle->QualifierOrder = {"type", "const", "volatile"};
} else if (QualifierAlignmentOrder.contains("type")) {
FormatStyle->QualifierAlignment = FormatStyle::QAS_Custom;
SmallVector<StringRef> Qualifiers;
QualifierAlignmentOrder.split(Qualifiers, " ", /*MaxSplit=*/-1,
/*KeepEmpty=*/false);
FormatStyle->QualifierOrder = {Qualifiers.begin(), Qualifiers.end()};
}
if (SortIncludes) {
FormatStyle->SortIncludes = {};
FormatStyle->SortIncludes.Enabled = true;
}
unsigned CursorPosition = Cursor;
Replacements Replaces = sortIncludes(*FormatStyle, code->getBuffer(), ranges,
AssumedFileName, &CursorPosition);
// To format JSON insert a variable to trick the code into thinking its
// JavaScript.
if (FormatStyle->isJson() && !FormatStyle->DisableFormat) {
auto err =
Replaces.add(tooling::Replacement(AssumedFileName, 0, 0, "x = "));
if (err)
return Err("Bad Json variable insertion");
}
auto ChangedCode =
cantFail(tooling::applyAllReplacements(code->getBuffer(), Replaces));
// Get new affected ranges after sorting `#includes`.
ranges = tooling::calculateRangesAfterReplacements(Replaces, ranges);
FormattingAttemptStatus Status;
Replacements FormatChanges =
reformat(*FormatStyle, ChangedCode, ranges, AssumedFileName, &Status);
Replaces = Replaces.merge(FormatChanges);
return Ok(
cantFail(tooling::applyAllReplacements(code->getBuffer(), Replaces)));
}
static auto format_range(const std::string str,
const std::string assumedFileName,
const std::string style, const bool is_line_range,
const std::vector<unsigned> ranges) -> Result {
ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
MemoryBuffer::getMemBuffer(str);
if (std::error_code EC = CodeOrErr.getError())
return Err(EC.message());
std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get());
if (Code->getBufferSize() == 0)
return Ok(""); // Empty files are formatted correctly.
std::vector<tooling::Range> Ranges;
if (ranges.empty()) {
fillRanges(Code.get(), Ranges);
return format_range(std::move(Code), assumedFileName, style,
std::move(Ranges));
}
IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
new llvm::vfs::InMemoryFileSystem);
FileManager Files(FileSystemOptions(), InMemoryFileSystem);
DiagnosticOptions DiagOpts;
DiagnosticsEngine Diagnostics(
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), DiagOpts);
SourceManager Sources(Diagnostics, Files);
FileID ID = createInMemoryFile("<irrelevant>", *Code, Sources, Files,
InMemoryFileSystem.get());
if (is_line_range) {
for (auto FromLine = begin(ranges); FromLine < end(ranges); FromLine += 2) {
auto ToLine = FromLine + 1;
SourceLocation Start = Sources.translateLineCol(ID, *FromLine, 1);
SourceLocation End = Sources.translateLineCol(ID, *ToLine, UINT_MAX);
if (Start.isInvalid() || End.isInvalid())
return Err("invalid line number");
unsigned Offset = Sources.getFileOffset(Start);
unsigned Length = Sources.getFileOffset(End) - Offset;
Ranges.push_back(tooling::Range(Offset, Length));
}
} else {
if (ranges.size() > 2 && ranges.size() % 2 != 0)
return Err("number of -offset and -length arguments must match");
if (ranges.size() == 1) {
auto offset = begin(ranges);
if (*offset >= Code->getBufferSize()) {
std::stringstream err;
err << "offset " << *offset << " is outside the file";
return Err(err.str());
}
SourceLocation Start =
Sources.getLocForStartOfFile(ID).getLocWithOffset(*offset);
SourceLocation End = Sources.getLocForEndOfFile(ID);
unsigned Offset = Sources.getFileOffset(Start);
unsigned Length = Sources.getFileOffset(End) - Offset;
Ranges.push_back(tooling::Range(Offset, Length));
} else {
for (auto offset = begin(ranges); offset < end(ranges); offset += 2) {
auto length = offset + 1;
if (*offset >= Code->getBufferSize()) {
std::stringstream err;
err << "offset " << *offset << " is outside the file";
return Err(err.str());
}
unsigned end = *offset + *length;
if (end > Code->getBufferSize()) {
std::stringstream err;
err << "invalid length " << *length << ", offset + length (" << end
<< ") is outside the file.";
return Err(err.str());
}
SourceLocation Start =
Sources.getLocForStartOfFile(ID).getLocWithOffset(*offset);
SourceLocation End = Start.getLocWithOffset(*length);
unsigned Offset = Sources.getFileOffset(Start);
unsigned Length = Sources.getFileOffset(End) - Offset;
Ranges.push_back(tooling::Range(Offset, Length));
}
}
}
return format_range(std::move(Code), assumedFileName, style,
std::move(Ranges));
}
static auto format(const std::string str, const std::string assumedFileName,
const std::string style) -> Result {
ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
MemoryBuffer::getMemBuffer(str);
if (std::error_code EC = CodeOrErr.getError())
return Err(EC.message());
std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get());
if (Code->getBufferSize() == 0)
return Ok(""); // Empty files are formatted correctly.
std::vector<tooling::Range> Ranges;
fillRanges(Code.get(), Ranges);
return format_range(std::move(Code), assumedFileName, style,
std::move(Ranges));
}
} // namespace format
} // namespace clang
auto version() -> std::string {
return clang::getClangToolFullVersion("clang-format");
}
auto format(const std::string str, const std::string assumedFileName,
const std::string style) -> Result {
return clang::format::format(str, assumedFileName, style);
}
auto format_byte(const std::string str, const std::string assumedFileName,
const std::string style, const std::vector<unsigned> ranges)
-> Result {
return clang::format::format_range(str, assumedFileName, style, false,
std::move(ranges));
}
auto format_line(const std::string str, const std::string assumedFileName,
const std::string style, const std::vector<unsigned> ranges)
-> Result {
return clang::format::format_range(str, assumedFileName, style, true,
std::move(ranges));
}
auto set_fallback_style(const std::string style) -> void {
FallbackStyle = style;
}
auto set_sort_includes(const bool sort) -> void { SortIncludes = sort; }
auto dump_config(const std::string style, const std::string FileName,
const std::string code) -> Result {
llvm::Expected<clang::format::FormatStyle> FormatStyle =
clang::format::getStyle(style, FileName, FallbackStyle, code);
if (!FormatStyle)
return Err(llvm::toString(FormatStyle.takeError()));
std::string Config = clang::format::configurationAsText(*FormatStyle);
return Ok(Config);
}

View File

@@ -0,0 +1,24 @@
#ifndef CLANG_FORMAT_WASM_LIB_H_
#define CLANG_FORMAT_WASM_LIB_H_
#include <sstream>
struct Result {
bool error;
std::string content;
};
auto version() -> std::string;
auto format(const std::string str, const std::string assumedFileName, const std::string style) -> Result;
auto format_byte(const std::string str,
const std::string assumedFileName,
const std::string style,
const std::vector<unsigned> ranges) -> Result;
auto format_line(const std::string str,
const std::string assumedFileName,
const std::string style,
const std::vector<unsigned> ranges) -> Result;
auto set_fallback_style(const std::string style) -> void;
auto set_sort_includes(const bool sort) -> void;
auto dump_config(const std::string style, const std::string FileName, const std::string code) -> Result;
#endif

View File

@@ -0,0 +1,146 @@
/* @ts-self-types="./clang-format.d.ts" */
async function load(module) {
if (typeof Response === "function" && module instanceof Response) {
if ("compileStreaming" in WebAssembly) {
try {
return await WebAssembly.compileStreaming(module);
} catch (e) {
if (module.headers.get("Content-Type") !== "application/wasm") {
console.warn(
"`WebAssembly.compileStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n",
e,
);
} else {
throw e;
}
}
}
return module.arrayBuffer();
}
return module;
}
let wasm;
export default async function initAsync(input) {
if (wasm !== undefined) {
return wasm;
}
if (typeof input === "undefined") {
input = new URL("clang-format.wasm", import.meta.url);
}
if (
typeof input === "string" ||
(typeof Request === "function" && input instanceof Request) ||
(typeof URL === "function" && input instanceof URL)
) {
input = fetch(input);
}
wasm = await load(await input).then((wasm) => Module({ wasm }));
assert_init = () => {};
}
function assert_init() {
throw new Error("uninit");
}
export function version() {
assert_init();
return wasm.version();
}
export function set_fallback_style(style) {
assert_init();
wasm.set_fallback_style(style);
}
export function set_sort_includes(sort) {
assert_init();
wasm.set_sort_includes(sort);
}
function unwrap(result) {
const { error, content } = result;
if (error) {
throw Error(content);
}
return content;
}
export function format(content, filename = "<stdin>", style = "LLVM") {
assert_init();
const result = wasm.format(content, filename, style);
return unwrap(result);
}
export function format_line_range(
content,
range,
filename = "<stdin>",
style = "LLVM",
) {
assert_init();
const rangeList = new wasm.RangeList();
for (const [fromLine, toLine] of range) {
if (fromLine < 1) {
throw Error("start line should be at least 1");
}
if (fromLine > toLine) {
throw Error("start line should not exceed end line");
}
rangeList.push_back(fromLine);
rangeList.push_back(toLine);
}
const result = wasm.format_line(content, filename, style, rangeList);
rangeList.delete();
return unwrap(result);
}
export function format_byte_range(
content,
range,
filename = "<stdin>",
style = "LLVM",
) {
assert_init();
const rangeList = new wasm.RangeList();
if (range.length === 1 && range[0].length === 1) {
rangeList.push_back(range[0][0]);
} else {
for (const [offset, length] of range) {
if (offset < 0) {
throw Error("start offset should be at least 0");
}
if (length < 0) {
throw Error("length should be at least 0");
}
rangeList.push_back(offset);
rangeList.push_back(length);
}
}
const result = wasm.format_byte(content, filename, style, rangeList);
rangeList.delete();
return unwrap(result);
}
export function dump_config({
style = "file",
filename = "<stdin>",
code = "",
} = {}) {
assert_init();
const result = wasm.dump_config(style, filename, code);
return unwrap(result);
}
export {
format_byte_range as formatByteRange,
format_line_range as formatLineRange,
};

View File

@@ -0,0 +1,42 @@
@echo off
rem Build script for Go Prettier Plugin WASM using TinyGo
rem This script compiles the Go code to WebAssembly for browser environment
echo Building Go Prettier Plugin WASM with TinyGo...
rem Check if TinyGo is available
tinygo version >nul 2>&1
if errorlevel 1 (
echo TinyGo not found! Please install TinyGo first.
echo Visit: https://tinygo.org/getting-started/install/
pause
exit /b 1
)
rem Display TinyGo version
echo Using TinyGo version:
tinygo version
rem Build the WASM file using TinyGo
echo Compiling main.go to go.wasm with TinyGo...
tinygo build -o go-format.wasm -target wasm main.go
if errorlevel 1 (
echo Build failed!
pause
exit /b 1
)
echo Build successful!
rem Show file size (Windows version)
for %%A in (go.wasm) do echo WASM file size: %%~zA bytes
rem Copy to public directory for browser access
if exist "..\..\..\..\..\public" (
copy go.wasm ..\..\..\..\..\public\go.wasm > nul
echo Copied to public directory
del go.wasm
echo Cleaned up local WASM file
)
echo Go Prettier Plugin WASM (TinyGo) is ready!

View File

@@ -0,0 +1,38 @@
#!/bin/bash
# Build script for Go Prettier Plugin WASM using TinyGo
# This script compiles the Go code to WebAssembly for browser environment
echo "Building Go Prettier Plugin WASM with TinyGo..."
# Check if TinyGo is available
if ! command -v tinygo &> /dev/null; then
echo "TinyGo not found! Please install TinyGo first."
echo "Visit: https://tinygo.org/getting-started/install/"
exit 1
fi
# Display TinyGo version
echo "Using TinyGo version: $(tinygo version)"
# Build the WASM file using TinyGo
echo "Compiling main.go to go.wasm with TinyGo..."
tinygo build -o go-format.wasm -target wasm main.go
if [ $? -ne 0 ]; then
echo "Build failed!"
exit 1
fi
echo "Build successful!"
echo "WASM file size: $(du -h go-format.wasm | cut -f1)"
# Copy to public directory for browser access
if [ -d "../../../../../public" ]; then
cp go-format.wasm ../../../../../public/go-format.wasm
echo "Copied to public directory"
rm go-format.wasm
echo "Cleaned up local WASM file"
fi
echo "Go Prettier Plugin WASM (TinyGo) is ready!"

View File

@@ -0,0 +1,43 @@
@echo off
rem Build script for Go Prettier Plugin WASM using native Go
rem This script compiles the Go code to WebAssembly for browser environment
echo Building Go Prettier Plugin WASM with native Go...
rem Check if Go is available
go version >nul 2>&1
if %ERRORLEVEL% NEQ 0 (
echo Go not found! Please install Go 1.21+ first.
echo Visit: https://golang.org/dl/
pause
exit /b 1
)
rem Set WASM build environment for browser (js/wasm)
set GOOS=js
set GOARCH=wasm
rem Build the WASM file using native Go
echo Compiling main.go to go.wasm with Go...
go build -o go-format.wasm main.go
if %ERRORLEVEL% EQU 0 (
echo Build successful!
rem Show file size (Windows version)
for %%A in (go.wasm) do echo WASM file size: %%~zA bytes
rem Copy to public directory for browser access
if exist "..\..\..\..\..\public" (
copy go.wasm ..\..\..\..\..\public\go.wasm > nul
echo Copied to public directory
del go.wasm
echo Cleaned up local WASM file
)
echo Go Prettier Plugin WASM is ready!
) else (
echo Build failed!
pause
exit /b 1
)

View File

@@ -0,0 +1,42 @@
#!/bin/bash
# Build script for Go Prettier Plugin WASM using native Go
# This script compiles the Go code to WebAssembly for browser environment
echo "Building Go Prettier Plugin WASM with native Go..."
# Check if Go is available
if ! command -v go &> /dev/null; then
echo "Go not found! Please install Go 1.21+ first."
echo "Visit: https://golang.org/dl/"
exit 1
fi
# Display Go version
echo "Using Go version: $(go version)"
# Set WASM build environment for browser (js/wasm)
export GOOS=js
export GOARCH=wasm
# Build the WASM file using native Go
echo "Compiling main.go to go.wasm with Go..."
go build -o go-format.wasm main.go
if [ $? -eq 0 ]; then
echo "Build successful!"
echo "WASM file size: $(du -h go-format.wasm | cut -f1)"
# Copy to public directory for browser access
if [ -d "../../../../../public" ]; then
cp go-format.wasm ../../../../../public/go-format.wasm
echo "Copied to public directory"
rm go-format.wasm
echo "Cleaned up local WASM file"
fi
echo "Go Prettier Plugin WASM is ready!"
else
echo "Build failed!"
exit 1
fi

View File

@@ -0,0 +1,10 @@
import { Parser, Plugin } from "prettier";
export declare const languages: Plugin["languages"];
export declare const parsers: {
go: Parser;
};
export declare const printers: Plugin["printers"];
declare const plugin: Plugin;
export default plugin;

View File

@@ -0,0 +1,142 @@
/**
* @fileoverview Go Prettier Format Plugin
* A Prettier plugin for formatting Go code using WebAssembly.
* This plugin leverages Go's native formatting capabilities through WASM.
*/
import "./wasm_exec.js"
/** @type {Promise<void>|null} */
let initializePromise;
/**
* Initializes the Go WebAssembly module for formatting Go code.
* This function sets up the WASM runtime and makes the formatGo function
* available on the global object.
*
* @async
* @function initialize
* @returns {Promise<void>} A promise that resolves when the WASM module is ready
* @throws {Error} If the WASM file cannot be loaded or instantiated
*/
function initialize() {
if (initializePromise) {
return initializePromise;
}
initializePromise = (async () => {
const go = new TinyGo();
// Load WASM file from browser
const response = await fetch('/go-format.wasm');
if (!response.ok) {
throw new Error(`Failed to load WASM file: ${response.status} ${response.statusText}`);
}
const wasmBuffer = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(
wasmBuffer,
go.importObject
);
// go.run returns a promise that resolves when the go program exits.
// Since our program is a long-running service (it exposes a function and waits),
// we don't await it.
go.run(instance);
// The `formatGo` function is now available on the global object.
})();
return initializePromise;
}
/**
* Prettier language configuration for Go.
* Defines the language settings, file extensions, and parser mappings.
*
* @type {Array<Object>}
* @property {string} name - The display name of the language
* @property {string[]} parsers - Array of parser names for this language
* @property {string[]} extensions - File extensions associated with this language
* @property {string[]} vscodeLanguageIds - VSCode language identifier mappings
*/
const languages = [
{
name: "Go",
parsers: ["go-format"],
extensions: [".go"],
vscodeLanguageIds: ["go"],
},
];
/**
* Prettier parser configuration for Go.
* Defines how Go source code should be parsed and processed.
*
* @type {Object<string, Object>}
* @property {Object} go-format - Go language parser configuration
* @property {Function} go-format.parse - Parser function that returns the input text as-is
* @property {string} go-format.astFormat - AST format identifier for the printer
* @property {Function} go-format.locStart - Function to get the start location of a node
* @property {Function} go-format.locEnd - Function to get the end location of a node
*/
const parsers = {
"go-format": {
/**
* Parse Go source code. For this plugin, we pass through the text as-is
* since the actual formatting is handled by the Go WASM module.
*
* @param {string} text - The Go source code to parse
* @returns {string} The input text unchanged
*/
parse: (text) => text,
astFormat: "go-format",
// These are required for Prettier to work
/**
* Get the start location of a node in the source code.
*
* @param {string} node - The node (in this case, the source text)
* @returns {number} Always returns 0 as we treat the entire text as one node
*/
locStart: (node) => 0,
/**
* Get the end location of a node in the source code.
*
* @param {string} node - The node (in this case, the source text)
* @returns {number} The length of the text
*/
locEnd: (node) => node.length,
},
};
/**
* Prettier printer configuration for Go.
* Defines how the parsed Go AST should be formatted back to text.
*
* @type {Object<string, Object>}
* @property {Object} go-format - Go formatting printer configuration
* @property {Function} go-format.print - Async function that formats Go code
*/
const printers = {
"go-format": {
/**
* Format Go source code using the WebAssembly Go formatter.
* This function initializes the WASM module if needed and calls the
* global formatGo function exposed by the Go program.
*
* @async
* @param {Object} path - Prettier's path object containing the source code
* @param {Function} path.getValue - Function to get the current node value
* @returns {Promise<string>} The formatted Go source code
* @throws {Error} If the WASM module fails to initialize or format the code
*/
print: async (path) => {
// The WASM module must be initialized before we can format.
await initialize();
const text = path.getValue();
// The `formatGo` function is exposed on the global object by our Go program.
return globalThis.formatGo(text);
},
},
};
export default { languages, parsers, printers, initialize };

View File

@@ -0,0 +1,66 @@
//go:build js && wasm
// Package main implements a WebAssembly module that provides Go code formatting
// functionality for the Prettier plugin. This package exposes the formatGo function
// to JavaScript, enabling web-based Go code formatting using Go's built-in format package.
//
// The module is designed to be compiled to WebAssembly using native Go (GOOS=js GOARCH=wasm)
// and loaded in browser environments as part of the Go Prettier plugin.
package main
import (
"go/format"
"syscall/js"
)
// formatGo is a JavaScript-callable function that formats Go source code.
// It wraps the standard library's go/format.Source function to be accessible
// from JavaScript environments through WebAssembly.
//
// Parameters:
// - this: The JavaScript 'this' context (unused)
// - i: JavaScript arguments array where i[0] should contain the Go source code as a string
//
// Returns:
// - js.Value: The formatted Go source code as a JavaScript string value
// - If formatting fails due to syntax errors, returns the original code unchanged
// - If no arguments are provided, returns js.Null() and logs an error
//
// The function handles syntax errors gracefully by returning the original code
// and logging error details to the JavaScript console for debugging purposes.
func formatGo(this js.Value, i []js.Value) interface{} {
if len(i) == 0 {
js.Global().Get("console").Call("error", "formatGo: missing code argument")
return js.Null()
}
code := i[0].String()
formatted, err := format.Source([]byte(code))
if err != nil {
// In case of a syntax error in the Go code, go/format returns an error.
// Prettier expects the original text to be returned in case of an error.
// We also log the error to the console for debugging purposes.
js.Global().Get("console").Call("error", "Error formatting Go code:", err.Error())
return js.ValueOf(code)
}
return js.ValueOf(string(formatted))
}
// main initializes the WebAssembly module and exposes the formatGo function
// to the JavaScript global scope. The function sets up a blocking channel
// to prevent the WASM module from exiting, allowing it to serve as a
// long-running service for formatting operations.
//
// The exposed formatGo function can be called from JavaScript as:
//
// global.formatGo(sourceCode)
func main() {
// Create a channel to keep the Go program running.
// This is necessary because the WASM module would exit otherwise.
c := make(chan struct{}, 0)
// Expose the formatGo function to the JavaScript global scope.
js.Global().Set("formatGo", js.FuncOf(formatGo))
// Block forever
<-c
}

View File

@@ -0,0 +1,553 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// This file has been modified for use by the TinyGo compiler.
(() => {
// Map multiple JavaScript environments to a single common API,
// preferring web standards over Node.js API.
//
// Environments considered:
// - Browsers
// - Node.js
// - Electron
// - Parcel
if (typeof global !== "undefined") {
// global already exists
} else if (typeof window !== "undefined") {
window.global = window;
} else if (typeof self !== "undefined") {
self.global = self;
} else {
throw new Error("cannot export Go (neither global, window nor self is defined)");
}
if (!global.require && typeof require !== "undefined") {
global.require = require;
}
if (!global.fs && global.require) {
global.fs = require("node:fs");
}
const enosys = () => {
const err = new Error("not implemented");
err.code = "ENOSYS";
return err;
};
if (!global.fs) {
let outputBuf = "";
global.fs = {
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
writeSync(fd, buf) {
outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf("\n");
if (nl != -1) {
console.log(outputBuf.substr(0, nl));
outputBuf = outputBuf.substr(nl + 1);
}
return buf.length;
},
write(fd, buf, offset, length, position, callback) {
if (offset !== 0 || length !== buf.length || position !== null) {
callback(enosys());
return;
}
const n = this.writeSync(fd, buf);
callback(null, n);
},
chmod(path, mode, callback) { callback(enosys()); },
chown(path, uid, gid, callback) { callback(enosys()); },
close(fd, callback) { callback(enosys()); },
fchmod(fd, mode, callback) { callback(enosys()); },
fchown(fd, uid, gid, callback) { callback(enosys()); },
fstat(fd, callback) { callback(enosys()); },
fsync(fd, callback) { callback(null); },
ftruncate(fd, length, callback) { callback(enosys()); },
lchown(path, uid, gid, callback) { callback(enosys()); },
link(path, link, callback) { callback(enosys()); },
lstat(path, callback) { callback(enosys()); },
mkdir(path, perm, callback) { callback(enosys()); },
open(path, flags, mode, callback) { callback(enosys()); },
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
readdir(path, callback) { callback(enosys()); },
readlink(path, callback) { callback(enosys()); },
rename(from, to, callback) { callback(enosys()); },
rmdir(path, callback) { callback(enosys()); },
stat(path, callback) { callback(enosys()); },
symlink(path, link, callback) { callback(enosys()); },
truncate(path, length, callback) { callback(enosys()); },
unlink(path, callback) { callback(enosys()); },
utimes(path, atime, mtime, callback) { callback(enosys()); },
};
}
if (!global.process) {
global.process = {
getuid() { return -1; },
getgid() { return -1; },
geteuid() { return -1; },
getegid() { return -1; },
getgroups() { throw enosys(); },
pid: -1,
ppid: -1,
umask() { throw enosys(); },
cwd() { throw enosys(); },
chdir() { throw enosys(); },
}
}
if (!global.crypto) {
const nodeCrypto = require("node:crypto");
global.crypto = {
getRandomValues(b) {
nodeCrypto.randomFillSync(b);
},
};
}
if (!global.performance) {
global.performance = {
now() {
const [sec, nsec] = process.hrtime();
return sec * 1000 + nsec / 1000000;
},
};
}
if (!global.TextEncoder) {
global.TextEncoder = require("node:util").TextEncoder;
}
if (!global.TextDecoder) {
global.TextDecoder = require("node:util").TextDecoder;
}
// End of polyfills for common API.
const encoder = new TextEncoder("utf-8");
const decoder = new TextDecoder("utf-8");
let reinterpretBuf = new DataView(new ArrayBuffer(8));
var logLine = [];
const wasmExit = {}; // thrown to exit via proc_exit (not an error)
global.TinyGo = class {
constructor() {
this._callbackTimeouts = new Map();
this._nextCallbackTimeoutID = 1;
const mem = () => {
// The buffer may change when requesting more memory.
return new DataView(this._inst.exports.memory.buffer);
}
const unboxValue = (v_ref) => {
reinterpretBuf.setBigInt64(0, v_ref, true);
const f = reinterpretBuf.getFloat64(0, true);
if (f === 0) {
return undefined;
}
if (!isNaN(f)) {
return f;
}
const id = v_ref & 0xffffffffn;
return this._values[id];
}
const loadValue = (addr) => {
let v_ref = mem().getBigUint64(addr, true);
return unboxValue(v_ref);
}
const boxValue = (v) => {
const nanHead = 0x7FF80000n;
if (typeof v === "number") {
if (isNaN(v)) {
return nanHead << 32n;
}
if (v === 0) {
return (nanHead << 32n) | 1n;
}
reinterpretBuf.setFloat64(0, v, true);
return reinterpretBuf.getBigInt64(0, true);
}
switch (v) {
case undefined:
return 0n;
case null:
return (nanHead << 32n) | 2n;
case true:
return (nanHead << 32n) | 3n;
case false:
return (nanHead << 32n) | 4n;
}
let id = this._ids.get(v);
if (id === undefined) {
id = this._idPool.pop();
if (id === undefined) {
id = BigInt(this._values.length);
}
this._values[id] = v;
this._goRefCounts[id] = 0;
this._ids.set(v, id);
}
this._goRefCounts[id]++;
let typeFlag = 1n;
switch (typeof v) {
case "string":
typeFlag = 2n;
break;
case "symbol":
typeFlag = 3n;
break;
case "function":
typeFlag = 4n;
break;
}
return id | ((nanHead | typeFlag) << 32n);
}
const storeValue = (addr, v) => {
let v_ref = boxValue(v);
mem().setBigUint64(addr, v_ref, true);
}
const loadSlice = (array, len, cap) => {
return new Uint8Array(this._inst.exports.memory.buffer, array, len);
}
const loadSliceOfValues = (array, len, cap) => {
const a = new Array(len);
for (let i = 0; i < len; i++) {
a[i] = loadValue(array + i * 8);
}
return a;
}
const loadString = (ptr, len) => {
return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len));
}
const timeOrigin = Date.now() - performance.now();
this.importObject = {
wasi_snapshot_preview1: {
// https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write
fd_write: function(fd, iovs_ptr, iovs_len, nwritten_ptr) {
let nwritten = 0;
if (fd == 1) {
for (let iovs_i=0; iovs_i<iovs_len;iovs_i++) {
let iov_ptr = iovs_ptr+iovs_i*8; // assuming wasm32
let ptr = mem().getUint32(iov_ptr + 0, true);
let len = mem().getUint32(iov_ptr + 4, true);
nwritten += len;
for (let i=0; i<len; i++) {
let c = mem().getUint8(ptr+i);
if (c == 13) { // CR
// ignore
} else if (c == 10) { // LF
// write line
let line = decoder.decode(new Uint8Array(logLine));
logLine = [];
console.log(line);
} else {
logLine.push(c);
}
}
}
} else {
console.error('invalid file descriptor:', fd);
}
mem().setUint32(nwritten_ptr, nwritten, true);
return 0;
},
fd_close: () => 0, // dummy
fd_fdstat_get: () => 0, // dummy
fd_seek: () => 0, // dummy
proc_exit: (code) => {
this.exited = true;
this.exitCode = code;
this._resolveExitPromise();
throw wasmExit;
},
random_get: (bufPtr, bufLen) => {
crypto.getRandomValues(loadSlice(bufPtr, bufLen));
return 0;
},
},
gojs: {
// func ticks() int64
"runtime.ticks": () => {
return BigInt((timeOrigin + performance.now()) * 1e6);
},
// func sleepTicks(timeout int64)
"runtime.sleepTicks": (timeout) => {
// Do not sleep, only reactivate scheduler after the given timeout.
setTimeout(() => {
if (this.exited) return;
try {
this._inst.exports.go_scheduler();
} catch (e) {
if (e !== wasmExit) throw e;
}
}, Number(timeout)/1e6);
},
// func finalizeRef(v ref)
"syscall/js.finalizeRef": (v_ref) => {
// Note: TinyGo does not support finalizers so this is only called
// for one specific case, by js.go:jsString. and can/might leak memory.
const id = v_ref & 0xffffffffn;
if (this._goRefCounts?.[id] !== undefined) {
this._goRefCounts[id]--;
if (this._goRefCounts[id] === 0) {
const v = this._values[id];
this._values[id] = null;
this._ids.delete(v);
this._idPool.push(id);
}
} else {
console.error("syscall/js.finalizeRef: unknown id", id);
}
},
// func stringVal(value string) ref
"syscall/js.stringVal": (value_ptr, value_len) => {
value_ptr >>>= 0;
const s = loadString(value_ptr, value_len);
return boxValue(s);
},
// func valueGet(v ref, p string) ref
"syscall/js.valueGet": (v_ref, p_ptr, p_len) => {
let prop = loadString(p_ptr, p_len);
let v = unboxValue(v_ref);
let result = Reflect.get(v, prop);
return boxValue(result);
},
// func valueSet(v ref, p string, x ref)
"syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => {
const v = unboxValue(v_ref);
const p = loadString(p_ptr, p_len);
const x = unboxValue(x_ref);
Reflect.set(v, p, x);
},
// func valueDelete(v ref, p string)
"syscall/js.valueDelete": (v_ref, p_ptr, p_len) => {
const v = unboxValue(v_ref);
const p = loadString(p_ptr, p_len);
Reflect.deleteProperty(v, p);
},
// func valueIndex(v ref, i int) ref
"syscall/js.valueIndex": (v_ref, i) => {
return boxValue(Reflect.get(unboxValue(v_ref), i));
},
// valueSetIndex(v ref, i int, x ref)
"syscall/js.valueSetIndex": (v_ref, i, x_ref) => {
Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref));
},
// func valueCall(v ref, m string, args []ref) (ref, bool)
"syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => {
const v = unboxValue(v_ref);
const name = loadString(m_ptr, m_len);
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
try {
const m = Reflect.get(v, name);
storeValue(ret_addr, Reflect.apply(m, v, args));
mem().setUint8(ret_addr + 8, 1);
} catch (err) {
storeValue(ret_addr, err);
mem().setUint8(ret_addr + 8, 0);
}
},
// func valueInvoke(v ref, args []ref) (ref, bool)
"syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => {
try {
const v = unboxValue(v_ref);
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
storeValue(ret_addr, Reflect.apply(v, undefined, args));
mem().setUint8(ret_addr + 8, 1);
} catch (err) {
storeValue(ret_addr, err);
mem().setUint8(ret_addr + 8, 0);
}
},
// func valueNew(v ref, args []ref) (ref, bool)
"syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => {
const v = unboxValue(v_ref);
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
try {
storeValue(ret_addr, Reflect.construct(v, args));
mem().setUint8(ret_addr + 8, 1);
} catch (err) {
storeValue(ret_addr, err);
mem().setUint8(ret_addr+ 8, 0);
}
},
// func valueLength(v ref) int
"syscall/js.valueLength": (v_ref) => {
return unboxValue(v_ref).length;
},
// valuePrepareString(v ref) (ref, int)
"syscall/js.valuePrepareString": (ret_addr, v_ref) => {
const s = String(unboxValue(v_ref));
const str = encoder.encode(s);
storeValue(ret_addr, str);
mem().setInt32(ret_addr + 8, str.length, true);
},
// valueLoadString(v ref, b []byte)
"syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => {
const str = unboxValue(v_ref);
loadSlice(slice_ptr, slice_len, slice_cap).set(str);
},
// func valueInstanceOf(v ref, t ref) bool
"syscall/js.valueInstanceOf": (v_ref, t_ref) => {
return unboxValue(v_ref) instanceof unboxValue(t_ref);
},
// func copyBytesToGo(dst []byte, src ref) (int, bool)
"syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => {
let num_bytes_copied_addr = ret_addr;
let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable
const dst = loadSlice(dest_addr, dest_len);
const src = unboxValue(src_ref);
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
mem().setUint8(returned_status_addr, 0); // Return "not ok" status
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
mem().setUint32(num_bytes_copied_addr, toCopy.length, true);
mem().setUint8(returned_status_addr, 1); // Return "ok" status
},
// copyBytesToJS(dst ref, src []byte) (int, bool)
// Originally copied from upstream Go project, then modified:
// https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416
"syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => {
let num_bytes_copied_addr = ret_addr;
let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable
const dst = unboxValue(dst_ref);
const src = loadSlice(src_addr, src_len);
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
mem().setUint8(returned_status_addr, 0); // Return "not ok" status
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
mem().setUint32(num_bytes_copied_addr, toCopy.length, true);
mem().setUint8(returned_status_addr, 1); // Return "ok" status
},
}
};
// Go 1.20 uses 'env'. Go 1.21 uses 'gojs'.
// For compatibility, we use both as long as Go 1.20 is supported.
this.importObject.env = this.importObject.gojs;
}
async run(instance) {
this._inst = instance;
this._values = [ // JS values that Go currently has references to, indexed by reference id
NaN,
0,
null,
true,
false,
global,
this,
];
this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id
this._ids = new Map(); // mapping from JS values to reference ids
this._idPool = []; // unused ids that have been garbage collected
this.exited = false; // whether the Go program has exited
this.exitCode = 0;
if (this._inst.exports._start) {
let exitPromise = new Promise((resolve, reject) => {
this._resolveExitPromise = resolve;
});
// Run program, but catch the wasmExit exception that's thrown
// to return back here.
try {
this._inst.exports._start();
} catch (e) {
if (e !== wasmExit) throw e;
}
await exitPromise;
return this.exitCode;
} else {
this._inst.exports._initialize();
}
}
_resume() {
if (this.exited) {
throw new Error("Go program has already exited");
}
try {
this._inst.exports.resume();
} catch (e) {
if (e !== wasmExit) throw e;
}
if (this.exited) {
this._resolveExitPromise();
}
}
_makeFuncWrapper(id) {
const go = this;
return function () {
const event = { id: id, this: this, args: arguments };
go._pendingEvent = event;
go._resume();
return event.result;
};
}
}
if (
global.require &&
global.require.main === module &&
global.process &&
global.process.versions &&
!global.process.versions.electron
) {
if (process.argv.length != 3) {
console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
process.exit(1);
}
const go = new Go();
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => {
let exitCode = await go.run(result.instance);
process.exit(exitCode);
}).catch((err) => {
console.error(err);
process.exit(1);
});
}
})();

View File

@@ -0,0 +1,67 @@
/**
* Prettier Plugin for Groovy/Jenkins file formatting
*
* This plugin provides support for formatting Groovy and Jenkins files using the groovy-beautify library.
* It supports .groovy files and Jenkins-related files like Jenkinsfile.
*/
import type { Plugin, Parser, Printer } from 'prettier';
import groovyBeautify from 'groovy-beautify';
const parserName = 'groovy';
// 语言配置
const languages = [
{
name: 'Groovy',
aliases: ['groovy'],
parsers: [parserName],
filenames: ['jenkinsfile', 'Jenkinsfile'],
extensions: ['.jenkinsfile', '.Jenkinsfile', '.groovy'],
aceMode: 'groovy',
tmScope: 'source.groovy',
linguistLanguageId: 142,
vscodeLanguageIds: ['groovy']
},
];
// 解析器配置
const groovyParser: Parser<string> = {
astFormat: parserName,
parse: (text: string) => text,
locStart: () => 0,
locEnd: (node: string) => node.length,
};
// 打印器配置
const groovyPrinter: Printer<string> = {
print: (path, options) => {
try {
return groovyBeautify(path.node, {
width: options.printWidth || 80,
}).trim();
} catch (error) {
return path.node;
}
},
};
const options = {
};
// 插件对象
const groovyPlugin: Plugin = {
languages,
parsers: {
[parserName]: groovyParser,
},
printers: {
[parserName]: groovyPrinter,
},
options,
};
export default groovyPlugin;
export { languages };
export const parsers = groovyPlugin.parsers;
export const printers = groovyPlugin.printers;

View File

@@ -0,0 +1,17 @@
import type { IToken } from "java-parser";
import { type AstPath } from "prettier";
import { type JavaNode, type JavaNonTerminal, type JavaParserOptions } from "./printers/helpers.js";
export declare function determineFormatterOffOnRanges(cst: JavaNonTerminal): void;
export declare function isFullyBetweenFormatterOffOn(path: AstPath<JavaNode>): boolean;
export declare function canAttachComment(node: JavaNode): boolean;
export declare function handleLineComment(commentNode: JavaComment, _: string, options: JavaParserOptions): boolean;
export declare function handleRemainingComment(commentNode: JavaComment): boolean;
export type JavaComment = IToken & {
value: string;
leading: boolean;
trailing: boolean;
printed: boolean;
enclosingNode?: JavaNonTerminal;
precedingNode?: JavaNonTerminal;
followingNode?: JavaNonTerminal;
};

View File

@@ -0,0 +1,199 @@
import { util } from "prettier";
import parser from "./parser.js";
import { isEmptyStatement, isNonTerminal, isTerminal } from "./printers/helpers.js";
const formatterOffOnRangesByCst = new WeakMap();
export function determineFormatterOffOnRanges(cst) {
const { comments } = cst;
if (!comments) {
return;
}
const ranges = comments
.filter(({ image }) => /^(\/\/\s*@formatter:(off|on)\s*|\/\*\s*@formatter:(off|on)\s*\*\/)$/.test(image))
.reduce((ranges, { image, startOffset }) => {
const previous = ranges.at(-1);
if (image.endsWith("off")) {
if ((previous === null || previous === void 0 ? void 0 : previous.on) !== Infinity) {
ranges.push({ off: startOffset, on: Infinity });
}
}
else if ((previous === null || previous === void 0 ? void 0 : previous.on) === Infinity) {
previous.on = startOffset;
}
return ranges;
}, new Array());
formatterOffOnRangesByCst.set(cst, ranges);
}
export function isFullyBetweenFormatterOffOn(path) {
var _a;
const { node, root } = path;
const start = parser.locStart(node);
const end = parser.locEnd(node);
return (((_a = formatterOffOnRangesByCst
.get(root)) === null || _a === void 0 ? void 0 : _a.some(range => range.off < start && end < range.on)) === true);
}
export function canAttachComment(node) {
var _a, _b, _c;
if (isTerminal(node)) {
const { name, CATEGORIES } = node.tokenType;
return (name === "Identifier" ||
(CATEGORIES === null || CATEGORIES === void 0 ? void 0 : CATEGORIES.find(({ name }) => name === "BinaryOperator")) !== undefined);
}
const { children, name } = node;
switch (name) {
case "argumentList":
case "blockStatements":
case "emptyStatement":
case "enumBodyDeclarations":
return false;
case "annotationInterfaceMemberDeclaration":
case "classMemberDeclaration":
case "interfaceMemberDeclaration":
case "methodBody":
return !children.Semicolon;
case "blockStatement":
return !children.statement || !isEmptyStatement(children.statement[0]);
case "classBodyDeclaration":
return !((_a = children.classMemberDeclaration) === null || _a === void 0 ? void 0 : _a[0].children.Semicolon);
case "recordBodyDeclaration":
return !((_c = (_b = children.classBodyDeclaration) === null || _b === void 0 ? void 0 : _b[0].children.classMemberDeclaration) === null || _c === void 0 ? void 0 : _c[0].children.Semicolon);
case "statement":
return !isEmptyStatement(node);
case "statementWithoutTrailingSubstatement":
return !children.emptyStatement;
default:
return true;
}
}
export function handleLineComment(commentNode, _, options) {
return [
handleBinaryExpressionComments,
handleFqnOrRefTypeComments,
handleIfStatementComments,
handleJumpStatementComments,
handleLabeledStatementComments,
handleNameComments
].some(fn => fn(commentNode, options));
}
export function handleRemainingComment(commentNode) {
return [
handleFqnOrRefTypeComments,
handleMethodDeclaratorComments,
handleNameComments,
handleJumpStatementComments
].some(fn => fn(commentNode));
}
function handleBinaryExpressionComments(commentNode, options) {
const { enclosingNode, precedingNode, followingNode } = commentNode;
if (enclosingNode &&
isNonTerminal(enclosingNode) &&
enclosingNode.name === "binaryExpression") {
if (isBinaryOperator(followingNode)) {
if (options.experimentalOperatorPosition === "start") {
util.addLeadingComment(followingNode, commentNode);
}
else {
util.addTrailingComment(followingNode, commentNode);
}
return true;
}
else if (options.experimentalOperatorPosition === "start" &&
isBinaryOperator(precedingNode)) {
util.addLeadingComment(precedingNode, commentNode);
return true;
}
}
return false;
}
function handleFqnOrRefTypeComments(commentNode) {
const { enclosingNode, followingNode } = commentNode;
if (enclosingNode &&
isNonTerminal(enclosingNode) &&
enclosingNode.name === "fqnOrRefType" &&
followingNode) {
util.addLeadingComment(followingNode, commentNode);
return true;
}
return false;
}
function handleIfStatementComments(commentNode) {
const { enclosingNode, precedingNode } = commentNode;
if (enclosingNode &&
isNonTerminal(enclosingNode) &&
enclosingNode.name === "ifStatement" &&
precedingNode &&
isNonTerminal(precedingNode) &&
precedingNode.name === "statement") {
util.addDanglingComment(enclosingNode, commentNode, undefined);
return true;
}
return false;
}
function handleJumpStatementComments(commentNode) {
const { enclosingNode, precedingNode, followingNode } = commentNode;
if (enclosingNode &&
!precedingNode &&
!followingNode &&
isNonTerminal(enclosingNode) &&
["breakStatement", "continueStatement", "returnStatement"].includes(enclosingNode.name)) {
util.addTrailingComment(enclosingNode, commentNode);
return true;
}
return false;
}
function handleLabeledStatementComments(commentNode) {
const { enclosingNode, precedingNode } = commentNode;
if (enclosingNode &&
precedingNode &&
isNonTerminal(enclosingNode) &&
enclosingNode.name === "labeledStatement" &&
isTerminal(precedingNode) &&
precedingNode.tokenType.name === "Identifier") {
util.addLeadingComment(precedingNode, commentNode);
return true;
}
return false;
}
function handleMethodDeclaratorComments(commentNode) {
const { enclosingNode } = commentNode;
if (enclosingNode &&
isNonTerminal(enclosingNode) &&
enclosingNode.name === "methodDeclarator" &&
!enclosingNode.children.receiverParameter &&
!enclosingNode.children.formalParameterList &&
enclosingNode.children.LBrace[0].startOffset < commentNode.startOffset &&
commentNode.startOffset < enclosingNode.children.RBrace[0].startOffset) {
util.addDanglingComment(enclosingNode, commentNode, undefined);
return true;
}
return false;
}
function handleNameComments(commentNode) {
const { enclosingNode, precedingNode } = commentNode;
if (enclosingNode &&
precedingNode &&
isNonTerminal(enclosingNode) &&
isTerminal(precedingNode) &&
precedingNode.tokenType.name === "Identifier" &&
[
"ambiguousName",
"classOrInterfaceTypeToInstantiate",
"expressionName",
"moduleDeclaration",
"moduleName",
"packageDeclaration",
"packageName",
"packageOrTypeName",
"typeName"
].includes(enclosingNode.name)) {
util.addTrailingComment(precedingNode, commentNode);
return true;
}
return false;
}
function isBinaryOperator(node) {
var _a;
return (node !== undefined &&
(isNonTerminal(node)
? node.name === "shiftOperator"
: (_a = node.tokenType.CATEGORIES) === null || _a === void 0 ? void 0 : _a.some(({ name }) => name === "BinaryOperator")));
}

View File

@@ -0,0 +1,563 @@
import type { JavaNode } from "./printers/helpers.js";
declare const _default: {
languages: {
name: string;
parsers: "java"[];
group: string;
tmScope: string;
aceMode: string;
codemirrorMode: string;
codemirrorMimeType: string;
extensions: string[];
linguistLanguageId: number;
vscodeLanguageIds: string[];
}[];
parsers: {
java: {
parse(text: string, options: import("./printers/helpers.js").JavaParserOptions): import("./printers/helpers.js").JavaNonTerminal;
astFormat: string;
hasPragma(text: string): boolean;
locStart(node: JavaNode): number;
locEnd(node: JavaNode): number;
};
};
printers: {
java: {
print(path: import("prettier").AstPath<import("java-parser").ArrayInitializerCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").VariableInitializerListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").BlockCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").BlockStatementsCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").LocalVariableDeclarationStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").LocalVariableDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").LabeledStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ExpressionStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").IfStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").AssertStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").SwitchStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").SwitchBlockCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").SwitchBlockStatementGroupCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").SwitchLabelCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").SwitchRuleCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").WhileStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").DoStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").BasicForStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").StatementExpressionListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").EnhancedForStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").BreakStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ContinueStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ReturnStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ThrowStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").SynchronizedStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TryStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").CatchesCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").CatchClauseCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").CatchFormalParameterCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").CatchTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").FinallyCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TryWithResourcesStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ResourceSpecificationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ResourceListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").YieldStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ForInitCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ForUpdateCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").StatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").StatementWithoutTrailingSubstatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ForStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").BlockStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").CaseConstantCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").CasePatternCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").EmptyStatementCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").StatementExpressionCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").LocalVariableTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ResourceCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").VariableAccessCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ClassDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").NormalClassDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TypeParametersCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TypeParameterListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ClassExtendsCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ClassImplementsCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").InterfaceTypeListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ClassBodyCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ClassMemberDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").FieldDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").VariableDeclaratorListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").VariableDeclaratorCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").VariableDeclaratorIdCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").UnannPrimitiveTypeWithOptionalDimsSuffixCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").UnannReferenceTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").MethodDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").MethodHeaderCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").MethodDeclaratorCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ReceiverParameterCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").FormalParameterListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").VariableParaRegularParameterCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").VariableArityParameterCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ThrowsCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ExceptionTypeListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").StaticInitializerCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ConstructorDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ConstructorDeclaratorCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ConstructorBodyCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").UnqualifiedExplicitConstructorInvocationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").QualifiedExplicitConstructorInvocationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").EnumDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").EnumBodyCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").EnumConstantListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").EnumConstantCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").EnumBodyDeclarationsCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").RecordDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").RecordHeaderCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").RecordComponentListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").RecordComponentCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").VariableArityRecordComponentCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").RecordBodyCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").CompactConstructorDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").UnannTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").VariableInitializerCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").VariableModifierCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").UnannClassTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ClassBodyDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").InstanceInitializerCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ClassModifierCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ClassPermitsCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").FieldModifierCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").MethodModifierCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").MethodBodyCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ConstructorModifierCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").SimpleTypeNameCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ExplicitConstructorInvocationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").EnumConstantModifierCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ExceptionTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").FormalParameterCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ResultCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").RecordBodyDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").RecordComponentModifierCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").UnannClassOrInterfaceTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").UnannInterfaceTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").UnannPrimitiveTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").UnannTypeVariableCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").LambdaExpressionCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").LambdaParametersCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").LambdaParametersWithBracesCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ConciseLambdaParameterListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").NormalLambdaParameterListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").RegularLambdaParameterCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ConditionalExpressionCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").BinaryExpressionCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").UnaryExpressionCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").UnaryExpressionNotPlusMinusCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").PrimaryCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").PrimarySuffixCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").FqnOrRefTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").FqnOrRefTypePartFirstCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").FqnOrRefTypePartRestCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").FqnOrRefTypePartCommonCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ParenthesisExpressionCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").PrimitiveCastExpressionCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ReferenceTypeCastExpressionCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").UnqualifiedClassInstanceCreationExpressionCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ClassOrInterfaceTypeToInstantiateCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").MethodInvocationSuffixCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ArgumentListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ArrayCreationExpressionCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ArrayCreationExpressionWithoutInitializerSuffixCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ArrayCreationWithInitializerSuffixCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").DimExprsCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").DimExprCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ClassLiteralSuffixCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ArrayAccessSuffixCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").MethodReferenceSuffixCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").StringTemplateCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TextBlockTemplateCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").RecordPatternCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ComponentPatternListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").GuardCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TemplateCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").PatternCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ExpressionCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TypePatternCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").CastExpressionCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TypeArgumentsOrDiamondCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").DiamondCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ComponentPatternCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").MatchAllPatternCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ConciseLambdaParameterCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").EmbeddedExpressionCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").LambdaBodyCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").LambdaParameterListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").NormalLambdaParameterCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").LambdaParameterTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").NewExpressionCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").PrimaryPrefixCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TemplateArgumentCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").InterfaceDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").NormalInterfaceDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").InterfaceExtendsCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").InterfaceBodyCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").InterfaceMemberDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ConstantDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").InterfaceMethodDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").AnnotationInterfaceDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").AnnotationInterfaceBodyCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").AnnotationInterfaceMemberDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").AnnotationInterfaceElementDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").DefaultValueCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").AnnotationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ElementValuePairListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ElementValuePairCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ElementValueArrayInitializerCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ElementValueListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ElementValueCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").AnnotationInterfaceElementModifierCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ConstantModifierCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").InterfaceModifierCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").InterfacePermitsCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").InterfaceMethodModifierCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").LiteralCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ShiftOperatorCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").BooleanLiteralCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").FloatingPointLiteralCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").IntegerLiteralCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").MethodNameCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").AmbiguousNameCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TypeNameCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TypeIdentifierCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ExpressionNameCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").PackageNameCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ModuleNameCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").PackageOrTypeNameCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").CompilationUnitCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").OrdinaryCompilationUnitCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ModularCompilationUnitCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").PackageDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ImportDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TypeDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ModuleDeclarationCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").RequiresModuleDirectiveCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ExportsModuleDirectiveCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").OpensModuleDirectiveCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").UsesModuleDirectiveCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ProvidesModuleDirectiveCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ModuleDirectiveCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").RequiresModifierCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").PackageModifierCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").PrimitiveTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ReferenceTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TypeVariableCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").DimsCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TypeParameterCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TypeBoundCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").AdditionalBoundCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TypeArgumentsCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TypeArgumentListCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").WildcardCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").WildcardBoundsCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").InterfaceTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ClassTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").NumericTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").ClassOrInterfaceTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").FloatingPointTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").IntegralTypeCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TypeArgumentCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").TypeParameterModifierCstNode & {
comments?: import("./comments.js").JavaComment[];
}> | import("prettier").AstPath<import("java-parser").IToken & {
comments?: import("./comments.js").JavaComment[];
}>, options: import("prettier").ParserOptions<JavaNode>, print: (path: import("prettier").AstPath<JavaNode>) => import("prettier").Doc, args: unknown): import("prettier/doc.js").builders.Doc;
hasPrettierIgnore(path: import("prettier").AstPath<JavaNode>): boolean;
canAttachComment: typeof import("./comments.js").canAttachComment;
isBlockComment(node: JavaNode): boolean;
printComment(commentPath: import("prettier").AstPath<JavaNode>): string | import("prettier/doc.js").builders.Doc[];
getCommentChildNodes(node: JavaNode): any[];
handleComments: {
ownLine: typeof import("./comments.js").handleLineComment;
endOfLine: typeof import("./comments.js").handleLineComment;
remaining: typeof import("./comments.js").handleRemainingComment;
};
};
};
options: {
entrypoint: {
type: "choice";
category: string;
default: string;
choices: {
value: string;
description: string;
}[];
description: string;
};
arrowParens: {
type: "choice";
category: string;
default: string;
choices: {
value: string;
description: string;
}[];
description: string;
};
trailingComma: {
type: "choice";
category: string;
default: string;
choices: {
value: string;
description: string;
}[];
description: string;
};
experimentalOperatorPosition: {
type: "choice";
category: string;
default: string;
choices: {
value: string;
description: string;
}[];
description: string;
};
};
defaultOptions: {
arrowParens: "avoid";
};
};
export default _default;

View File

@@ -0,0 +1,29 @@
import options from "./options.js";
import parser from "./parser.js";
import printer from "./printer.js";
export default {
languages: [
{
name: "Java",
parsers: ["java"],
group: "Java",
tmScope: "source.java",
aceMode: "java",
codemirrorMode: "clike",
codemirrorMimeType: "text/x-java",
extensions: [".java"],
linguistLanguageId: 181,
vscodeLanguageIds: ["java"]
}
],
parsers: {
java: parser
},
printers: {
java: printer
},
options,
defaultOptions: {
arrowParens: "avoid"
}
};

View File

@@ -0,0 +1,43 @@
declare const _default: {
entrypoint: {
type: "choice";
category: string;
default: string;
choices: {
value: string;
description: string;
}[];
description: string;
};
arrowParens: {
type: "choice";
category: string;
default: string;
choices: {
value: string;
description: string;
}[];
description: string;
};
trailingComma: {
type: "choice";
category: string;
default: string;
choices: {
value: string;
description: string;
}[];
description: string;
};
experimentalOperatorPosition: {
type: "choice";
category: string;
default: string;
choices: {
value: string;
description: string;
}[];
description: string;
};
};
export default _default;

View File

@@ -0,0 +1,284 @@
export default {
entrypoint: {
type: "choice",
category: "Global",
default: "compilationUnit",
// sed -nr 's/.*\.RULE\(([^,]+),.*/\1/p' $(ls path/to/java-parser/rules/folder/*)
choices: [
{ value: "arrayInitializer", description: "" },
{ value: "variableInitializerList", description: "" },
{ value: "block", description: "" },
{ value: "blockStatements", description: "" },
{ value: "blockStatement", description: "" },
{ value: "localVariableDeclarationStatement", description: "" },
{ value: "localVariableDeclaration", description: "" },
{ value: "localVariableType", description: "" },
{ value: "statement", description: "" },
{ value: "statementWithoutTrailingSubstatement", description: "" },
{ value: "emptyStatement", description: "" },
{ value: "labeledStatement", description: "" },
{ value: "expressionStatement", description: "" },
{ value: "statementExpression", description: "" },
{ value: "ifStatement", description: "" },
{ value: "assertStatement", description: "" },
{ value: "switchStatement", description: "" },
{ value: "switchBlock", description: "" },
{ value: "switchBlockStatementGroup", description: "" },
{ value: "switchLabel", description: "" },
{ value: "switchRule", description: "" },
{ value: "caseConstant", description: "" },
{ value: "casePattern", description: "" },
{ value: "whileStatement", description: "" },
{ value: "doStatement", description: "" },
{ value: "forStatement", description: "" },
{ value: "basicForStatement", description: "" },
{ value: "forInit", description: "" },
{ value: "forUpdate", description: "" },
{ value: "statementExpressionList", description: "" },
{ value: "enhancedForStatement", description: "" },
{ value: "breakStatement", description: "" },
{ value: "continueStatement", description: "" },
{ value: "returnStatement", description: "" },
{ value: "throwStatement", description: "" },
{ value: "synchronizedStatement", description: "" },
{ value: "tryStatement", description: "" },
{ value: "catches", description: "" },
{ value: "catchClause", description: "" },
{ value: "catchFormalParameter", description: "" },
{ value: "catchType", description: "" },
{ value: "finally", description: "" },
{ value: "tryWithResourcesStatement", description: "" },
{ value: "resourceSpecification", description: "" },
{ value: "resourceList", description: "" },
{ value: "resource", description: "" },
{ value: "yieldStatement", description: "" },
{ value: "variableAccess", description: "" },
{ value: "classDeclaration", description: "" },
{ value: "normalClassDeclaration", description: "" },
{ value: "classModifier", description: "" },
{ value: "typeParameters", description: "" },
{ value: "typeParameterList", description: "" },
{ value: "classExtends", description: "" },
{ value: "classImplements", description: "" },
{ value: "interfaceTypeList", description: "" },
{ value: "classPermits", description: "" },
{ value: "classBody", description: "" },
{ value: "classBodyDeclaration", description: "" },
{ value: "classMemberDeclaration", description: "" },
{ value: "fieldDeclaration", description: "" },
{ value: "fieldModifier", description: "" },
{ value: "variableDeclaratorList", description: "" },
{ value: "variableDeclarator", description: "" },
{ value: "variableDeclaratorId", description: "" },
{ value: "variableInitializer", description: "" },
{ value: "unannType", description: "" },
{ value: "unannPrimitiveTypeWithOptionalDimsSuffix", description: "" },
{ value: "unannPrimitiveType", description: "" },
{ value: "unannReferenceType", description: "" },
{ value: "unannClassOrInterfaceType", description: "" },
{ value: "unannClassType", description: "" },
{ value: "unannInterfaceType", description: "" },
{ value: "unannTypeVariable", description: "" },
{ value: "methodDeclaration", description: "" },
{ value: "methodModifier", description: "" },
{ value: "methodHeader", description: "" },
{ value: "result", description: "" },
{ value: "methodDeclarator", description: "" },
{ value: "receiverParameter", description: "" },
{ value: "formalParameterList", description: "" },
{ value: "formalParameter", description: "" },
{ value: "variableParaRegularParameter", description: "" },
{ value: "variableArityParameter", description: "" },
{ value: "variableModifier", description: "" },
{ value: "throws", description: "" },
{ value: "exceptionTypeList", description: "" },
{ value: "exceptionType", description: "" },
{ value: "methodBody", description: "" },
{ value: "instanceInitializer", description: "" },
{ value: "staticInitializer", description: "" },
{ value: "constructorDeclaration", description: "" },
{ value: "constructorModifier", description: "" },
{ value: "constructorDeclarator", description: "" },
{ value: "simpleTypeName", description: "" },
{ value: "constructorBody", description: "" },
{ value: "explicitConstructorInvocation", description: "" },
{ value: "unqualifiedExplicitConstructorInvocation", description: "" },
{ value: "qualifiedExplicitConstructorInvocation", description: "" },
{ value: "enumDeclaration", description: "" },
{ value: "enumBody", description: "" },
{ value: "enumConstantList", description: "" },
{ value: "enumConstant", description: "" },
{ value: "enumConstantModifier", description: "" },
{ value: "enumBodyDeclarations", description: "" },
{ value: "recordDeclaration", description: "" },
{ value: "recordHeader", description: "" },
{ value: "recordComponentList", description: "" },
{ value: "recordComponent", description: "" },
{ value: "variableArityRecordComponent", description: "" },
{ value: "recordComponentModifier", description: "" },
{ value: "recordBody", description: "" },
{ value: "recordBodyDeclaration", description: "" },
{ value: "compactConstructorDeclaration", description: "" },
{ value: "isDims", description: "" },
{ value: "expression", description: "" },
{ value: "lambdaExpression", description: "" },
{ value: "lambdaParameters", description: "" },
{ value: "lambdaParametersWithBraces", description: "" },
{ value: "lambdaParameterList", description: "" },
{ value: "conciseLambdaParameterList", description: "" },
{ value: "normalLambdaParameterList", description: "" },
{ value: "normalLambdaParameter", description: "" },
{ value: "regularLambdaParameter", description: "" },
{ value: "lambdaParameterType", description: "" },
{ value: "conciseLambdaParameter", description: "" },
{ value: "lambdaBody", description: "" },
{ value: "conditionalExpression", description: "" },
{ value: "binaryExpression", description: "" },
{ value: "unaryExpression", description: "" },
{ value: "unaryExpressionNotPlusMinus", description: "" },
{ value: "primary", description: "" },
{ value: "primaryPrefix", description: "" },
{ value: "primarySuffix", description: "" },
{ value: "fqnOrRefType", description: "" },
{ value: "fqnOrRefTypePartRest", description: "" },
{ value: "fqnOrRefTypePartCommon", description: "" },
{ value: "fqnOrRefTypePartFirst", description: "" },
{ value: "parenthesisExpression", description: "" },
{ value: "castExpression", description: "" },
{ value: "primitiveCastExpression", description: "" },
{ value: "referenceTypeCastExpression", description: "" },
{ value: "newExpression", description: "" },
{ value: "unqualifiedClassInstanceCreationExpression", description: "" },
{ value: "classOrInterfaceTypeToInstantiate", description: "" },
{ value: "typeArgumentsOrDiamond", description: "" },
{ value: "diamond", description: "" },
{ value: "methodInvocationSuffix", description: "" },
{ value: "argumentList", description: "" },
{ value: "arrayCreationExpression", description: "" },
{
value: "arrayCreationExpressionWithoutInitializerSuffix",
description: ""
},
{ value: "arrayCreationWithInitializerSuffix", description: "" },
{ value: "dimExprs", description: "" },
{ value: "dimExpr", description: "" },
{ value: "classLiteralSuffix", description: "" },
{ value: "arrayAccessSuffix", description: "" },
{ value: "methodReferenceSuffix", description: "" },
{ value: "templateArgument", description: "" },
{ value: "template", description: "" },
{ value: "stringTemplate", description: "" },
{ value: "textBlockTemplate", description: "" },
{ value: "embeddedExpression", description: "" },
{ value: "pattern", description: "" },
{ value: "typePattern", description: "" },
{ value: "recordPattern", description: "" },
{ value: "componentPatternList", description: "" },
{ value: "componentPattern", description: "" },
{ value: "matchAllPattern", description: "" },
{ value: "guard", description: "" },
{ value: "isRefTypeInMethodRef", description: "" },
{ value: "interfaceDeclaration", description: "" },
{ value: "normalInterfaceDeclaration", description: "" },
{ value: "interfaceModifier", description: "" },
{ value: "interfaceExtends", description: "" },
{ value: "interfacePermits", description: "" },
{ value: "interfaceBody", description: "" },
{ value: "interfaceMemberDeclaration", description: "" },
{ value: "constantDeclaration", description: "" },
{ value: "constantModifier", description: "" },
{ value: "interfaceMethodDeclaration", description: "" },
{ value: "interfaceMethodModifier", description: "" },
{ value: "annotationInterfaceDeclaration", description: "" },
{ value: "annotationInterfaceBody", description: "" },
{ value: "annotationInterfaceMemberDeclaration", description: "" },
{ value: "annotationInterfaceElementDeclaration", description: "" },
{ value: "annotationInterfaceElementModifier", description: "" },
{ value: "defaultValue", description: "" },
{ value: "annotation", description: "" },
{ value: "elementValuePairList", description: "" },
{ value: "elementValuePair", description: "" },
{ value: "elementValue", description: "" },
{ value: "elementValueArrayInitializer", description: "" },
{ value: "elementValueList", description: "" },
{ value: "literal", description: "" },
{ value: "integerLiteral", description: "" },
{ value: "floatingPointLiteral", description: "" },
{ value: "booleanLiteral", description: "" },
{ value: "shiftOperator", description: "" },
{ value: "moduleName", description: "" },
{ value: "packageName", description: "" },
{ value: "typeName", description: "" },
{ value: "expressionName", description: "" },
{ value: "methodName", description: "" },
{ value: "packageOrTypeName", description: "" },
{ value: "ambiguousName", description: "" },
{ value: "compilationUnit", description: "" },
{ value: "ordinaryCompilationUnit", description: "" },
{ value: "modularCompilationUnit", description: "" },
{ value: "packageDeclaration", description: "" },
{ value: "packageModifier", description: "" },
{ value: "importDeclaration", description: "" },
{ value: "typeDeclaration", description: "" },
{ value: "moduleDeclaration", description: "" },
{ value: "moduleDirective", description: "" },
{ value: "requiresModuleDirective", description: "" },
{ value: "exportsModuleDirective", description: "" },
{ value: "opensModuleDirective", description: "" },
{ value: "usesModuleDirective", description: "" },
{ value: "providesModuleDirective", description: "" },
{ value: "requiresModifier", description: "" },
{ value: "primitiveType", description: "" },
{ value: "numericType", description: "" },
{ value: "integralType", description: "" },
{ value: "floatingPointType", description: "" },
{ value: "referenceType", description: "" },
{ value: "classOrInterfaceType", description: "" },
{ value: "classType", description: "" },
{ value: "interfaceType", description: "" },
{ value: "typeVariable", description: "" },
{ value: "dims", description: "" },
{ value: "typeParameter", description: "" },
{ value: "typeParameterModifier", description: "" },
{ value: "typeBound", description: "" },
{ value: "additionalBound", description: "" },
{ value: "typeArguments", description: "" },
{ value: "typeArgumentList", description: "" },
{ value: "typeArgument", description: "" },
{ value: "wildcard", description: "" },
{ value: "wildcardBounds", description: "" }
],
description: "Prettify from the entrypoint, allowing to use prettier on snippet."
},
arrowParens: {
type: "choice",
category: "Java",
default: "always",
choices: [
{ value: "always", description: "" },
{ value: "avoid", description: "" }
],
description: "Include parentheses around a sole arrow function parameter."
},
trailingComma: {
type: "choice",
category: "Java",
default: "all",
choices: [
{ value: "all", description: "" },
{ value: "es5", description: "" },
{ value: "none", description: "" }
],
description: "Print trailing commas wherever possible when multi-line."
},
experimentalOperatorPosition: {
type: "choice",
category: "Java",
default: "end",
choices: [
{ value: "start", description: "" },
{ value: "end", description: "" }
],
description: "Where to print operators when binary expressions wrap lines."
}
};

View File

@@ -0,0 +1,9 @@
import { type JavaNode, type JavaNonTerminal, type JavaParserOptions } from "./printers/helpers.js";
declare const _default: {
parse(text: string, options: JavaParserOptions): JavaNonTerminal;
astFormat: string;
hasPragma(text: string): boolean;
locStart(node: JavaNode): number;
locEnd(node: JavaNode): number;
};
export default _default;

View File

@@ -0,0 +1,24 @@
import { parse } from "java-parser";
import { determineFormatterOffOnRanges } from "./comments.js";
import { isTerminal } from "./printers/helpers.js";
export default {
parse(text, options) {
var _a;
const cst = parse(text, options.entrypoint);
(_a = cst.comments) === null || _a === void 0 ? void 0 : _a.forEach(comment => {
comment.value = comment.image;
});
determineFormatterOffOnRanges(cst);
return cst;
},
astFormat: "java",
hasPragma(text) {
return /^\/\*\*\n\s+\*\s@(format|prettier)\n\s+\*\//.test(text);
},
locStart(node) {
return isTerminal(node) ? node.startOffset : node.location.startOffset;
},
locEnd(node) {
return (isTerminal(node) ? node.endOffset : node.location.endOffset) + 1;
}
};

View File

@@ -0,0 +1,18 @@
import type { AstPath } from "prettier";
import { canAttachComment, handleLineComment, handleRemainingComment } from "./comments.js";
import { type JavaNode } from "./printers/helpers.js";
declare const _default: {
print(path: DistributedAstPath<JavaNode>, options: import("prettier").ParserOptions<JavaNode>, print: (path: AstPath<JavaNode>) => import("prettier").Doc, args: unknown): import("prettier/doc.js").builders.Doc;
hasPrettierIgnore(path: AstPath<JavaNode>): boolean;
canAttachComment: typeof canAttachComment;
isBlockComment(node: JavaNode): boolean;
printComment(commentPath: AstPath<JavaNode>): string | import("prettier/doc.js").builders.Doc[];
getCommentChildNodes(node: JavaNode): any[];
handleComments: {
ownLine: typeof handleLineComment;
endOfLine: typeof handleLineComment;
remaining: typeof handleRemainingComment;
};
};
export default _default;
type DistributedAstPath<T> = T extends any ? AstPath<T> : never;

View File

@@ -0,0 +1,40 @@
import { canAttachComment, handleLineComment, handleRemainingComment, isFullyBetweenFormatterOffOn } from "./comments.js";
import { isNonTerminal, isTerminal, printComment } from "./printers/helpers.js";
import { printerForNodeType } from "./printers/index.js";
export default {
print(path, options, print, args) {
return hasTerminal(path)
? path.node.image
: printerForNodeType(path.node.name)(path, print, options, args);
},
hasPrettierIgnore(path) {
var _a;
const { node } = path;
return (((_a = node.comments) === null || _a === void 0 ? void 0 : _a.some(({ image }) => /^(\/\/\s*prettier-ignore|\/\*\s*prettier-ignore\s*\*\/)$/.test(image))) === true ||
(canAttachComment(node) && isFullyBetweenFormatterOffOn(path)));
},
canAttachComment,
isBlockComment(node) {
return isTerminal(node) && node.tokenType.name === "TraditionalComment";
},
printComment(commentPath) {
const { node } = commentPath;
if (isNonTerminal(node) || node.tokenType.GROUP !== "comments") {
throw new Error(`Not a comment: ${JSON.stringify(node)}`);
}
return printComment(node);
},
getCommentChildNodes(node) {
return isNonTerminal(node)
? Object.values(node.children).flatMap(child => child)
: [];
},
handleComments: {
ownLine: handleLineComment,
endOfLine: handleLineComment,
remaining: handleRemainingComment
}
};
function hasTerminal(path) {
return isTerminal(path.node);
}

View File

@@ -0,0 +1,9 @@
declare const _default: {
arrayInitializer(path: import("prettier").AstPath<import("java-parser").ArrayInitializerCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn, options: import("./helpers.js").JavaParserOptions): import("prettier/doc.js").builders.Group | "{}";
variableInitializerList(path: import("prettier").AstPath<import("java-parser").VariableInitializerListCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): import("prettier/doc.js").builders.Doc[];
};
export default _default;

View File

@@ -0,0 +1,9 @@
import { printArrayInitializer, printList } from "./helpers.js";
export default {
arrayInitializer(path, print, options) {
return printArrayInitializer(path, print, options, "variableInitializerList");
},
variableInitializerList(path, print) {
return printList(path, print, "variableInitializer");
}
};

View File

@@ -0,0 +1,117 @@
import { builders } from "prettier/doc";
import { printSingle } from "./helpers.js";
declare const _default: {
block(path: import("prettier").AstPath<import("java-parser").BlockCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
blockStatements(path: import("prettier").AstPath<import("java-parser").BlockStatementsCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
blockStatement: typeof printSingle;
localVariableDeclarationStatement(path: import("prettier").AstPath<import("java-parser").LocalVariableDeclarationStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
localVariableDeclaration(path: import("prettier").AstPath<import("java-parser").LocalVariableDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
localVariableType: typeof printSingle;
statement: typeof printSingle;
statementWithoutTrailingSubstatement: typeof printSingle;
emptyStatement(): string;
labeledStatement(path: import("prettier").AstPath<import("java-parser").LabeledStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
expressionStatement(path: import("prettier").AstPath<import("java-parser").ExpressionStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
statementExpression: typeof printSingle;
ifStatement(path: import("prettier").AstPath<import("java-parser").IfStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
assertStatement(path: import("prettier").AstPath<import("java-parser").AssertStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
switchStatement(path: import("prettier").AstPath<import("java-parser").SwitchStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
switchBlock(path: import("prettier").AstPath<import("java-parser").SwitchBlockCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
switchBlockStatementGroup(path: import("prettier").AstPath<import("java-parser").SwitchBlockStatementGroupCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
switchLabel(path: import("prettier").AstPath<import("java-parser").SwitchLabelCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): "default" | builders.Group | builders.Doc[];
switchRule(path: import("prettier").AstPath<import("java-parser").SwitchRuleCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
caseConstant: typeof printSingle;
casePattern: typeof printSingle;
whileStatement(path: import("prettier").AstPath<import("java-parser").WhileStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
doStatement(path: import("prettier").AstPath<import("java-parser").DoStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): (string | builders.Group | builders.Doc[])[];
forStatement: typeof printSingle;
basicForStatement(path: import("prettier").AstPath<import("java-parser").BasicForStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
forInit: typeof printSingle;
forUpdate: typeof printSingle;
statementExpressionList(path: import("prettier").AstPath<import("java-parser").StatementExpressionListCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Group;
enhancedForStatement(path: import("prettier").AstPath<import("java-parser").EnhancedForStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Group;
breakStatement(path: import("prettier").AstPath<import("java-parser").BreakStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[] | "break;";
continueStatement(path: import("prettier").AstPath<import("java-parser").ContinueStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[] | "continue;";
returnStatement(path: import("prettier").AstPath<import("java-parser").ReturnStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
throwStatement(path: import("prettier").AstPath<import("java-parser").ThrowStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
synchronizedStatement(path: import("prettier").AstPath<import("java-parser").SynchronizedStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
tryStatement(path: import("prettier").AstPath<import("java-parser").TryStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc;
catches(path: import("prettier").AstPath<import("java-parser").CatchesCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
catchClause(path: import("prettier").AstPath<import("java-parser").CatchClauseCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
catchFormalParameter(path: import("prettier").AstPath<import("java-parser").CatchFormalParameterCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
catchType(path: import("prettier").AstPath<import("java-parser").CatchTypeCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
finally(path: import("prettier").AstPath<import("java-parser").FinallyCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
tryWithResourcesStatement(path: import("prettier").AstPath<import("java-parser").TryWithResourcesStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
resourceSpecification(path: import("prettier").AstPath<import("java-parser").ResourceSpecificationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Group | "()";
resourceList(path: import("prettier").AstPath<import("java-parser").ResourceListCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
resource: typeof printSingle;
yieldStatement(path: import("prettier").AstPath<import("java-parser").YieldStatementCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
variableAccess: typeof printSingle;
};
export default _default;

View File

@@ -0,0 +1,337 @@
import { builders } from "prettier/doc";
import { call, definedKeys, indentInParentheses, isBinaryExpression, isEmptyStatement, lineEndWithComments, lineStartWithComments, map, onlyDefinedKey, printBlock, printDanglingComments, printSingle, printWithModifiers } from "./helpers.js";
const { group, hardline, ifBreak, indent, join, line, softline } = builders;
export default {
block(path, print) {
const statements = path.node.children.blockStatements
? call(path, print, "blockStatements")
: [];
return printBlock(path, statements.length ? [statements] : []);
},
blockStatements(path, print) {
return join(hardline, map(path, statementPath => {
const { node, previous } = statementPath;
const statement = print(statementPath);
return previous &&
lineStartWithComments(node) > lineEndWithComments(previous) + 1
? [hardline, statement]
: statement;
}, "blockStatement").filter(doc => doc !== ""));
},
blockStatement: printSingle,
localVariableDeclarationStatement(path, print) {
return [call(path, print, "localVariableDeclaration"), ";"];
},
localVariableDeclaration(path, print) {
const declaration = join(" ", [
call(path, print, "localVariableType"),
call(path, print, "variableDeclaratorList")
]);
return printWithModifiers(path, print, "variableModifier", declaration);
},
localVariableType: printSingle,
statement: printSingle,
statementWithoutTrailingSubstatement: printSingle,
emptyStatement() {
return "";
},
labeledStatement(path, print) {
return [
call(path, print, "Identifier"),
": ",
call(path, print, "statement")
];
},
expressionStatement(path, print) {
return [call(path, print, "statementExpression"), ";"];
},
statementExpression: printSingle,
ifStatement(path, print) {
var _a;
const { children } = path.node;
const hasEmptyStatement = isEmptyStatement(children.statement[0]);
const statements = map(path, print, "statement");
const statement = [
"if ",
indentInParentheses(call(path, print, "expression")),
hasEmptyStatement ? ";" : [" ", statements[0]]
];
if (children.Else) {
const danglingComments = printDanglingComments(path);
if (danglingComments.length) {
statement.push(hardline, ...danglingComments, hardline);
}
else {
const elseHasBlock = ((_a = children.statement[0].children
.statementWithoutTrailingSubstatement) === null || _a === void 0 ? void 0 : _a[0].children.block) !==
undefined;
statement.push(elseHasBlock ? " " : hardline);
}
const elseHasEmptyStatement = isEmptyStatement(children.statement[1]);
statement.push("else", elseHasEmptyStatement ? ";" : [" ", statements[1]]);
}
return statement;
},
assertStatement(path, print) {
return ["assert ", ...join([" : "], map(path, print, "expression")), ";"];
},
switchStatement(path, print) {
return join(" ", [
"switch",
indentInParentheses(call(path, print, "expression")),
call(path, print, "switchBlock")
]);
},
switchBlock(path, print) {
const { children } = path.node;
const caseKeys = definedKeys(children, [
"switchBlockStatementGroup",
"switchRule"
]);
const cases = caseKeys.length === 1 ? map(path, print, caseKeys[0]) : [];
return printBlock(path, cases);
},
switchBlockStatementGroup(path, print) {
var _a, _b;
const { children } = path.node;
const switchLabel = call(path, print, "switchLabel");
if (!children.blockStatements) {
return [switchLabel, ":"];
}
const blockStatements = call(path, print, "blockStatements");
const statements = children.blockStatements[0].children.blockStatement;
const onlyStatementIsBlock = statements.length === 1 &&
((_b = (_a = statements[0].children.statement) === null || _a === void 0 ? void 0 : _a[0].children.statementWithoutTrailingSubstatement) === null || _b === void 0 ? void 0 : _b[0].children.block) !== undefined;
return [
switchLabel,
":",
onlyStatementIsBlock
? [" ", blockStatements]
: indent([hardline, blockStatements])
];
},
switchLabel(path, print) {
var _a, _b;
const { children } = path.node;
if (!((_b = (_a = children.caseConstant) !== null && _a !== void 0 ? _a : children.casePattern) !== null && _b !== void 0 ? _b : children.Null)) {
return "default";
}
const values = [];
if (children.Null) {
values.push("null");
if (children.Default) {
values.push("default");
}
}
else {
const valuesKey = onlyDefinedKey(children, [
"caseConstant",
"casePattern"
]);
values.push(...map(path, print, valuesKey));
}
const hasMultipleValues = values.length > 1;
const label = hasMultipleValues
? ["case", indent([line, ...join([",", line], values)])]
: ["case ", values[0]];
return children.guard
? [
group([...label, hasMultipleValues ? line : " "]),
call(path, print, "guard")
]
: group(label);
},
switchRule(path, print) {
const { children } = path.node;
const bodyKey = onlyDefinedKey(children, [
"block",
"expression",
"throwStatement"
]);
const parts = [
call(path, print, "switchLabel"),
" -> ",
call(path, print, bodyKey)
];
if (children.Semicolon) {
parts.push(";");
}
return parts;
},
caseConstant: printSingle,
casePattern: printSingle,
whileStatement(path, print) {
const statement = call(path, print, "statement");
const hasEmptyStatement = isEmptyStatement(path.node.children.statement[0]);
return [
"while ",
indentInParentheses(call(path, print, "expression")),
...[hasEmptyStatement ? ";" : " ", statement]
];
},
doStatement(path, print) {
const hasEmptyStatement = isEmptyStatement(path.node.children.statement[0]);
return [
"do",
hasEmptyStatement ? ";" : [" ", call(path, print, "statement")],
" while ",
indentInParentheses(call(path, print, "expression")),
";"
];
},
forStatement: printSingle,
basicForStatement(path, print) {
const { children } = path.node;
const danglingComments = printDanglingComments(path);
if (danglingComments.length) {
danglingComments.push(hardline);
}
const expressions = ["forInit", "expression", "forUpdate"].map(expressionKey => expressionKey in children ? call(path, print, expressionKey) : "");
const hasEmptyStatement = isEmptyStatement(children.statement[0]);
return [
...danglingComments,
"for ",
expressions.some(expression => expression !== "")
? indentInParentheses(join([";", line], expressions))
: "(;;)",
hasEmptyStatement ? ";" : [" ", call(path, print, "statement")]
];
},
forInit: printSingle,
forUpdate: printSingle,
statementExpressionList(path, print) {
return group(map(path, print, "statementExpression").map((expression, index) => index === 0 ? expression : [",", indent([line, expression])]));
},
enhancedForStatement(path, print) {
var _a;
const statementNode = path.node.children.statement[0];
const forStatement = [
printDanglingComments(path),
"for ",
"(",
call(path, print, "localVariableDeclaration"),
" : ",
call(path, print, "expression"),
")"
];
if (isEmptyStatement(statementNode)) {
forStatement.push(";");
}
else {
const hasStatementBlock = ((_a = statementNode.children.statementWithoutTrailingSubstatement) === null || _a === void 0 ? void 0 : _a[0].children.block) !== undefined;
const statement = call(path, print, "statement");
forStatement.push(hasStatementBlock ? [" ", statement] : indent([line, statement]));
}
return group(forStatement);
},
breakStatement(path, print) {
return path.node.children.Identifier
? ["break ", call(path, print, "Identifier"), ";"]
: "break;";
},
continueStatement(path, print) {
return path.node.children.Identifier
? ["continue ", call(path, print, "Identifier"), ";"]
: "continue;";
},
returnStatement(path, print) {
const { children } = path.node;
const statement = ["return"];
if (children.expression) {
statement.push(" ");
const expression = call(path, print, "expression");
if (isBinaryExpression(children.expression[0])) {
statement.push(group([
ifBreak("("),
indent([softline, expression]),
softline,
ifBreak(")")
]));
}
else {
statement.push(expression);
}
}
statement.push(";");
return statement;
},
throwStatement(path, print) {
return ["throw ", call(path, print, "expression"), ";"];
},
synchronizedStatement(path, print) {
return [
"synchronized ",
indentInParentheses(call(path, print, "expression")),
" ",
call(path, print, "block")
];
},
tryStatement(path, print) {
const { children } = path.node;
if (children.tryWithResourcesStatement) {
return call(path, print, "tryWithResourcesStatement");
}
const blocks = ["try", call(path, print, "block")];
if (children.catches) {
blocks.push(call(path, print, "catches"));
}
if (children.finally) {
blocks.push(call(path, print, "finally"));
}
return join(" ", blocks);
},
catches(path, print) {
return join(" ", map(path, print, "catchClause"));
},
catchClause(path, print) {
return [
"catch ",
indentInParentheses(call(path, print, "catchFormalParameter")),
" ",
call(path, print, "block")
];
},
catchFormalParameter(path, print) {
return join(" ", [
...map(path, print, "variableModifier"),
call(path, print, "catchType"),
call(path, print, "variableDeclaratorId")
]);
},
catchType(path, print) {
return join([line, "| "], [call(path, print, "unannClassType"), ...map(path, print, "classType")]);
},
finally(path, print) {
return ["finally ", call(path, print, "block")];
},
tryWithResourcesStatement(path, print) {
const { children } = path.node;
const blocks = [
"try",
call(path, print, "resourceSpecification"),
call(path, print, "block")
];
if (children.catches) {
blocks.push(call(path, print, "catches"));
}
if (children.finally) {
blocks.push(call(path, print, "finally"));
}
return join(" ", blocks);
},
resourceSpecification(path, print) {
const resources = [call(path, print, "resourceList")];
if (path.node.children.Semicolon) {
resources.push(ifBreak(";"));
}
return indentInParentheses(resources);
},
resourceList(path, print) {
return join([";", line], map(path, print, "resource"));
},
resource: printSingle,
yieldStatement(path, print) {
return ["yield ", call(path, print, "expression"), ";"];
},
variableAccess: printSingle
};

View File

@@ -0,0 +1,157 @@
import type { ClassBodyCstNode, EnumBodyDeclarationsCstNode } from "java-parser";
import type { AstPath } from "prettier";
import { builders } from "prettier/doc";
import { printClassPermits, printClassType, printSingle, type JavaPrintFn } from "./helpers.js";
declare const _default: {
classDeclaration(path: AstPath<import("java-parser").ClassDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
normalClassDeclaration(path: AstPath<import("java-parser").NormalClassDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
classModifier: typeof printSingle;
typeParameters(path: AstPath<import("java-parser").TypeParametersCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Group;
typeParameterList(path: AstPath<import("java-parser").TypeParameterListCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
classExtends(path: AstPath<import("java-parser").ClassExtendsCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
classImplements(path: AstPath<import("java-parser").ClassImplementsCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Group;
classPermits: typeof printClassPermits;
interfaceTypeList(path: AstPath<import("java-parser").InterfaceTypeListCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Group;
classBody(path: AstPath<ClassBodyCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
classBodyDeclaration: typeof printSingle;
classMemberDeclaration(path: AstPath<import("java-parser").ClassMemberDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc;
fieldDeclaration(path: AstPath<import("java-parser").FieldDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
fieldModifier: typeof printSingle;
variableDeclaratorList(path: AstPath<import("java-parser").VariableDeclaratorListCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Group | builders.Doc[];
variableDeclarator(path: AstPath<import("java-parser").VariableDeclaratorCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc;
variableDeclaratorId(path: AstPath<import("java-parser").VariableDeclaratorIdCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc;
variableInitializer: typeof printSingle;
unannType: typeof printSingle;
unannPrimitiveTypeWithOptionalDimsSuffix(path: AstPath<import("java-parser").UnannPrimitiveTypeWithOptionalDimsSuffixCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc;
unannPrimitiveType: typeof printSingle;
unannReferenceType(path: AstPath<import("java-parser").UnannReferenceTypeCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc;
unannClassOrInterfaceType: typeof printSingle;
unannClassType: typeof printClassType;
unannInterfaceType: typeof printSingle;
unannTypeVariable: typeof printSingle;
methodDeclaration(path: AstPath<import("java-parser").MethodDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
methodModifier: typeof printSingle;
methodHeader(path: AstPath<import("java-parser").MethodHeaderCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Group;
result: typeof printSingle;
methodDeclarator(path: AstPath<import("java-parser").MethodDeclaratorCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
receiverParameter(path: AstPath<import("java-parser").ReceiverParameterCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
formalParameterList(path: AstPath<import("java-parser").FormalParameterListCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
formalParameter: typeof printSingle;
variableParaRegularParameter(path: AstPath<import("java-parser").VariableParaRegularParameterCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
variableArityParameter(path: AstPath<import("java-parser").VariableArityParameterCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
variableModifier: typeof printSingle;
throws(path: AstPath<import("java-parser").ThrowsCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
exceptionTypeList(path: AstPath<import("java-parser").ExceptionTypeListCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
exceptionType: typeof printSingle;
methodBody: typeof printSingle;
instanceInitializer: typeof printSingle;
staticInitializer(path: AstPath<import("java-parser").StaticInitializerCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
constructorDeclaration(path: AstPath<import("java-parser").ConstructorDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
constructorModifier: typeof printSingle;
constructorDeclarator(path: AstPath<import("java-parser").ConstructorDeclaratorCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
simpleTypeName: typeof printSingle;
constructorBody(path: AstPath<import("java-parser").ConstructorBodyCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
explicitConstructorInvocation: typeof printSingle;
unqualifiedExplicitConstructorInvocation(path: AstPath<import("java-parser").UnqualifiedExplicitConstructorInvocationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
qualifiedExplicitConstructorInvocation(path: AstPath<import("java-parser").QualifiedExplicitConstructorInvocationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
enumDeclaration(path: AstPath<import("java-parser").EnumDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
enumBody(path: AstPath<import("java-parser").EnumBodyCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn, options: import("./helpers.js").JavaParserOptions): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
enumConstantList(path: AstPath<import("java-parser").EnumConstantListCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
enumConstant(path: AstPath<import("java-parser").EnumConstantCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
enumConstantModifier: typeof printSingle;
enumBodyDeclarations(path: AstPath<EnumBodyDeclarationsCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
recordDeclaration(path: AstPath<import("java-parser").RecordDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
recordHeader(path: AstPath<import("java-parser").RecordHeaderCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Group | "()";
recordComponentList(path: AstPath<import("java-parser").RecordComponentListCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
recordComponent(path: AstPath<import("java-parser").RecordComponentCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Group;
variableArityRecordComponent(path: AstPath<import("java-parser").VariableArityRecordComponentCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
recordComponentModifier: typeof printSingle;
recordBody(path: AstPath<import("java-parser").RecordBodyCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
recordBodyDeclaration: typeof printSingle;
compactConstructorDeclaration(path: AstPath<import("java-parser").CompactConstructorDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
};
export default _default;

View File

@@ -0,0 +1,446 @@
import { builders } from "prettier/doc";
import { call, each, hasDeclarationAnnotations, hasLeadingComments, indentInParentheses, isBinaryExpression, lineEndWithComments, lineStartWithComments, map, onlyDefinedKey, printBlock, printClassPermits, printClassType, printDanglingComments, printList, printSingle, printWithModifiers } from "./helpers.js";
const { group, hardline, indent, indentIfBreak, join, line, softline } = builders;
export default {
classDeclaration(path, print) {
const declarationKey = onlyDefinedKey(path.node.children, [
"enumDeclaration",
"normalClassDeclaration",
"recordDeclaration"
]);
const declaration = call(path, print, declarationKey);
return printWithModifiers(path, print, "classModifier", declaration, true);
},
normalClassDeclaration(path, print) {
const { classExtends, classImplements, classPermits, typeParameters } = path.node.children;
const header = ["class ", call(path, print, "typeIdentifier")];
if (typeParameters) {
header.push(call(path, print, "typeParameters"));
}
if (classExtends) {
header.push(indent([line, call(path, print, "classExtends")]));
}
if (classImplements) {
header.push(indent([line, call(path, print, "classImplements")]));
}
if (classPermits) {
header.push(indent([line, call(path, print, "classPermits")]));
}
return [group(header), " ", call(path, print, "classBody")];
},
classModifier: printSingle,
typeParameters(path, print) {
return group([
"<",
indent([softline, call(path, print, "typeParameterList")]),
softline,
">"
]);
},
typeParameterList(path, print) {
return printList(path, print, "typeParameter");
},
classExtends(path, print) {
return ["extends ", call(path, print, "classType")];
},
classImplements(path, print) {
return group([
"implements",
indent([line, call(path, print, "interfaceTypeList")])
]);
},
classPermits: printClassPermits,
interfaceTypeList(path, print) {
return group(printList(path, print, "interfaceType"));
},
classBody(path, print) {
return printBlock(path, printClassBodyDeclarations(path, print));
},
classBodyDeclaration: printSingle,
classMemberDeclaration(path, print) {
const { children } = path.node;
return children.Semicolon
? ""
: call(path, print, onlyDefinedKey(children));
},
fieldDeclaration(path, print) {
const declaration = [
call(path, print, "unannType"),
" ",
call(path, print, "variableDeclaratorList"),
";"
];
return printWithModifiers(path, print, "fieldModifier", declaration);
},
fieldModifier: printSingle,
variableDeclaratorList(path, print) {
var _a;
const declarators = map(path, print, "variableDeclarator");
return declarators.length > 1 &&
path.node.children.variableDeclarator.some(({ children }) => children.Equals)
? group(indent(join([",", line], declarators)), {
shouldBreak: ((_a = path.getNode(4)) === null || _a === void 0 ? void 0 : _a.name) !== "forInit"
})
: join(", ", declarators);
},
variableDeclarator(path, print) {
var _a, _b;
const { children } = path.node;
const variableInitializer = (_a = children.variableInitializer) === null || _a === void 0 ? void 0 : _a[0];
const declaratorId = call(path, print, "variableDeclaratorId");
if (!variableInitializer) {
return declaratorId;
}
const expression = (_b = variableInitializer.children.expression) === null || _b === void 0 ? void 0 : _b[0];
const declarator = [declaratorId, " ", call(path, print, "Equals")];
const initializer = call(path, print, "variableInitializer");
if (hasLeadingComments(variableInitializer) ||
(expression && isBinaryExpression(expression))) {
declarator.push(group(indent([line, initializer])));
}
else {
const groupId = Symbol("assignment");
declarator.push(group(indent(line), { id: groupId }), indentIfBreak(initializer, { groupId }));
}
return group(declarator);
},
variableDeclaratorId(path, print) {
const { dims, Underscore } = path.node.children;
if (Underscore) {
return "_";
}
const identifier = call(path, print, "Identifier");
return dims ? [identifier, call(path, print, "dims")] : identifier;
},
variableInitializer: printSingle,
unannType: printSingle,
unannPrimitiveTypeWithOptionalDimsSuffix(path, print) {
const type = call(path, print, "unannPrimitiveType");
return path.node.children.dims ? [type, call(path, print, "dims")] : type;
},
unannPrimitiveType: printSingle,
unannReferenceType(path, print) {
const type = call(path, print, "unannClassOrInterfaceType");
return path.node.children.dims ? [type, call(path, print, "dims")] : type;
},
unannClassOrInterfaceType: printSingle,
unannClassType: printClassType,
unannInterfaceType: printSingle,
unannTypeVariable: printSingle,
methodDeclaration(path, print) {
const declaration = [
call(path, print, "methodHeader"),
path.node.children.methodBody[0].children.Semicolon ? "" : " ",
call(path, print, "methodBody")
];
return printWithModifiers(path, print, "methodModifier", declaration);
},
methodModifier: printSingle,
methodHeader(path, print) {
const { typeParameters, annotation, throws } = path.node.children;
const header = [];
if (typeParameters) {
header.push(call(path, print, "typeParameters"));
}
if (annotation) {
header.push(join(line, map(path, print, "annotation")));
}
header.push(call(path, print, "result"), call(path, print, "methodDeclarator"));
return throws
? group([
...join(" ", header),
group(indent([line, call(path, print, "throws")]))
])
: group(join(" ", header));
},
result: printSingle,
methodDeclarator(path, print) {
const { dims, formalParameterList, receiverParameter } = path.node.children;
const declarator = [call(path, print, "Identifier")];
const parameters = [];
if (receiverParameter) {
parameters.push(call(path, print, "receiverParameter"));
}
if (formalParameterList) {
parameters.push(call(path, print, "formalParameterList"));
}
const items = parameters.length
? join([",", line], parameters)
: printDanglingComments(path);
declarator.push(items.length ? indentInParentheses(items) : "()");
if (dims) {
declarator.push(call(path, print, "dims"));
}
return declarator;
},
receiverParameter(path, print) {
return join(" ", [
...map(path, print, "annotation"),
call(path, print, "unannType"),
path.node.children.Identifier
? [call(path, print, "Identifier"), ".this"]
: "this"
]);
},
formalParameterList(path, print) {
return printList(path, print, "formalParameter");
},
formalParameter: printSingle,
variableParaRegularParameter(path, print) {
return join(" ", [
...map(path, print, "variableModifier"),
call(path, print, "unannType"),
call(path, print, "variableDeclaratorId")
]);
},
variableArityParameter(path, print) {
const type = join(" ", [
...map(path, print, "variableModifier"),
call(path, print, "unannType"),
...map(path, print, "annotation")
]);
return [type, "... ", call(path, print, "Identifier")];
},
variableModifier: printSingle,
throws(path, print) {
return ["throws ", call(path, print, "exceptionTypeList")];
},
exceptionTypeList(path, print) {
return join(", ", map(path, print, "exceptionType"));
},
exceptionType: printSingle,
methodBody: printSingle,
instanceInitializer: printSingle,
staticInitializer(path, print) {
return ["static ", call(path, print, "block")];
},
constructorDeclaration(path, print) {
const declaration = [call(path, print, "constructorDeclarator")];
if (path.node.children.throws) {
declaration.push(group(indent([line, call(path, print, "throws")])));
}
declaration.push(" ", call(path, print, "constructorBody"));
return printWithModifiers(path, print, "constructorModifier", declaration, true);
},
constructorModifier: printSingle,
constructorDeclarator(path, print) {
const { children } = path.node;
const parameters = [];
if (children.receiverParameter) {
parameters.push(call(path, print, "receiverParameter"));
}
if (children.formalParameterList) {
parameters.push(call(path, print, "formalParameterList"));
}
const header = [call(path, print, "simpleTypeName")];
header.push(parameters.length
? indentInParentheses(join([",", line], parameters))
: "()");
return children.typeParameters
? [call(path, print, "typeParameters"), " ", ...header]
: header;
},
simpleTypeName: printSingle,
constructorBody(path, print) {
const { children } = path.node;
const statements = [];
if (children.explicitConstructorInvocation) {
statements.push(call(path, print, "explicitConstructorInvocation"));
}
if (children.blockStatements) {
statements.push(call(path, print, "blockStatements"));
}
return printBlock(path, statements);
},
explicitConstructorInvocation: printSingle,
unqualifiedExplicitConstructorInvocation(path, print) {
const { children } = path.node;
const invocation = [];
if (children.typeArguments) {
invocation.push(call(path, print, "typeArguments"));
}
invocation.push(children.Super ? "super" : "this");
if (children.argumentList) {
invocation.push(group(["(", call(path, print, "argumentList"), ")"]));
}
else {
invocation.push(indentInParentheses(printDanglingComments(path), { shouldBreak: true }));
}
invocation.push(";");
return invocation;
},
qualifiedExplicitConstructorInvocation(path, print) {
const { children } = path.node;
const invocation = [call(path, print, "expressionName"), "."];
if (children.typeArguments) {
invocation.push(call(path, print, "typeArguments"));
}
invocation.push("super");
if (children.argumentList) {
invocation.push(group(["(", call(path, print, "argumentList"), ")"]));
}
else {
invocation.push(indentInParentheses(printDanglingComments(path), { shouldBreak: true }));
}
invocation.push(";");
return invocation;
},
enumDeclaration(path, print) {
const header = ["enum", call(path, print, "typeIdentifier")];
if (path.node.children.classImplements) {
header.push(call(path, print, "classImplements"));
}
return join(" ", [...header, call(path, print, "enumBody")]);
},
enumBody(path, print, options) {
var _a;
const { children } = path.node;
const contents = [];
const hasNonEmptyDeclaration = ((_a = children.enumBodyDeclarations) !== null && _a !== void 0 ? _a : [])
.flatMap(({ children }) => { var _a; return (_a = children.classBodyDeclaration) !== null && _a !== void 0 ? _a : []; })
.some(({ children }) => { var _a; return !((_a = children.classMemberDeclaration) === null || _a === void 0 ? void 0 : _a[0].children.Semicolon); });
if (children.enumConstantList) {
contents.push(call(path, print, "enumConstantList"));
if (!hasNonEmptyDeclaration && options.trailingComma !== "none") {
contents.push(",");
}
}
if (hasNonEmptyDeclaration) {
contents.push(";", hardline, call(path, print, "enumBodyDeclarations"));
}
return printBlock(path, contents.length ? [contents] : []);
},
enumConstantList(path, print) {
return join([",", hardline], map(path, constantPath => {
const constant = print(constantPath);
const { node, previous } = constantPath;
return !previous ||
lineStartWithComments(node) <= lineEndWithComments(previous) + 1
? constant
: [hardline, constant];
}, "enumConstant"));
},
enumConstant(path, print) {
const { argumentList, classBody } = path.node.children;
const initializer = [call(path, print, "Identifier")];
if (argumentList) {
initializer.push(group(["(", call(path, print, "argumentList"), ")"]));
}
if (classBody) {
initializer.push(" ", call(path, print, "classBody"));
}
return printWithModifiers(path, print, "enumConstantModifier", initializer);
},
enumConstantModifier: printSingle,
enumBodyDeclarations(path, print) {
return join(hardline, printClassBodyDeclarations(path, print));
},
recordDeclaration(path, print) {
const { children } = path.node;
const header = ["record ", call(path, print, "typeIdentifier")];
if (children.typeParameters) {
header.push(call(path, print, "typeParameters"));
}
header.push(call(path, print, "recordHeader"));
if (children.classImplements) {
header.push(" ", call(path, print, "classImplements"));
}
return [group(header), " ", call(path, print, "recordBody")];
},
recordHeader(path, print) {
return path.node.children.recordComponentList
? indentInParentheses(call(path, print, "recordComponentList"))
: indentInParentheses(printDanglingComments(path), { shouldBreak: true });
},
recordComponentList(path, print) {
return join([",", line], map(path, componentPath => {
const { node, previous } = componentPath;
const blankLine = previous &&
lineStartWithComments(node) > lineEndWithComments(previous) + 1;
const component = print(componentPath);
return blankLine ? [softline, component] : component;
}, "recordComponent"));
},
recordComponent(path, print) {
const { children } = path.node;
const component = [call(path, print, "unannType")];
if (children.Identifier ||
children.variableArityRecordComponent[0].children.annotation) {
component.push(" ");
}
const suffixKey = onlyDefinedKey(children, [
"Identifier",
"variableArityRecordComponent"
]);
component.push(call(path, print, suffixKey));
return group(join(line, [...map(path, print, "recordComponentModifier"), component]));
},
variableArityRecordComponent(path, print) {
return [
...join(" ", map(path, print, "annotation")),
"... ",
call(path, print, "Identifier")
];
},
recordComponentModifier: printSingle,
recordBody(path, print) {
const declarations = [];
let previousRequiresPadding = false;
each(path, declarationPath => {
var _a, _b, _c, _d;
const declaration = print(declarationPath);
if (declaration === "") {
return;
}
const { node, previous } = declarationPath;
const fieldDeclaration = (_c = (_b = (_a = node.children.classBodyDeclaration) === null || _a === void 0 ? void 0 : _a[0].children.classMemberDeclaration) === null || _b === void 0 ? void 0 : _b[0].children.fieldDeclaration) === null || _c === void 0 ? void 0 : _c[0].children;
const currentRequiresPadding = !fieldDeclaration ||
hasDeclarationAnnotations((_d = fieldDeclaration.fieldModifier) !== null && _d !== void 0 ? _d : []);
const blankLine = declarations.length > 0 &&
(previousRequiresPadding ||
currentRequiresPadding ||
lineStartWithComments(node) > lineEndWithComments(previous) + 1);
declarations.push(blankLine ? [hardline, declaration] : declaration);
previousRequiresPadding = currentRequiresPadding;
}, "recordBodyDeclaration");
return printBlock(path, declarations);
},
recordBodyDeclaration: printSingle,
compactConstructorDeclaration(path, print) {
const declaration = [
call(path, print, "simpleTypeName"),
" ",
call(path, print, "constructorBody")
];
return printWithModifiers(path, print, "constructorModifier", declaration, true);
}
};
function printClassBodyDeclarations(path, print) {
var _a;
if (!path.node.children.classBodyDeclaration) {
return [];
}
const declarations = [];
let previousRequiresPadding = path.node.name === "enumBodyDeclarations" ||
((_a = path.grandparent) === null || _a === void 0 ? void 0 : _a.name) ===
"normalClassDeclaration";
each(path, declarationPath => {
var _a, _b, _c;
const declaration = print(declarationPath);
if (declaration === "") {
return;
}
const { node, previous } = declarationPath;
const fieldDeclaration = (_b = (_a = node.children.classMemberDeclaration) === null || _a === void 0 ? void 0 : _a[0].children.fieldDeclaration) === null || _b === void 0 ? void 0 : _b[0].children;
const currentRequiresPadding = fieldDeclaration
? hasDeclarationAnnotations((_c = fieldDeclaration.fieldModifier) !== null && _c !== void 0 ? _c : [])
: true;
const blankLine = previousRequiresPadding ||
(declarations.length > 0 &&
(currentRequiresPadding ||
lineStartWithComments(node) > lineEndWithComments(previous) + 1));
declarations.push(blankLine ? [hardline, declaration] : declaration);
previousRequiresPadding = currentRequiresPadding;
}, "classBodyDeclaration");
return declarations;
}

View File

@@ -0,0 +1,134 @@
import type { StringTemplateCstNode, TextBlockTemplateCstNode } from "java-parser";
import type { AstPath } from "prettier";
import { builders } from "prettier/doc";
import type { JavaComment } from "../comments.js";
import { printSingle, type JavaPrintFn } from "./helpers.js";
declare const _default: {
expression: typeof printSingle;
lambdaExpression(path: AstPath<import("java-parser").LambdaExpressionCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn, _: import("./helpers.js").JavaParserOptions, args?: unknown): builders.Doc[];
lambdaParameters(path: AstPath<import("java-parser").LambdaParametersCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn, options: import("./helpers.js").JavaParserOptions): builders.Doc;
lambdaParametersWithBraces(path: AstPath<import("java-parser").LambdaParametersWithBracesCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn, options: import("./helpers.js").JavaParserOptions): builders.Doc;
lambdaParameterList: typeof printSingle;
conciseLambdaParameterList(path: AstPath<import("java-parser").ConciseLambdaParameterListCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
normalLambdaParameterList(path: AstPath<import("java-parser").NormalLambdaParameterListCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
normalLambdaParameter: typeof printSingle;
regularLambdaParameter(path: AstPath<import("java-parser").RegularLambdaParameterCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
lambdaParameterType: typeof printSingle;
conciseLambdaParameter: typeof printSingle;
lambdaBody: typeof printSingle;
conditionalExpression(path: AstPath<import("java-parser").ConditionalExpressionCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc;
binaryExpression(path: AstPath<import("java-parser").BinaryExpressionCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn, options: import("./helpers.js").JavaParserOptions): builders.Doc;
unaryExpression(path: AstPath<import("java-parser").UnaryExpressionCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
unaryExpressionNotPlusMinus(path: AstPath<import("java-parser").UnaryExpressionNotPlusMinusCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
primary(path: AstPath<import("java-parser").PrimaryCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc;
primaryPrefix: typeof printSingle;
primarySuffix(path: AstPath<import("java-parser").PrimarySuffixCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc;
fqnOrRefType(path: AstPath<import("java-parser").FqnOrRefTypeCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn, _: import("./helpers.js").JavaParserOptions, args: unknown): builders.Doc[];
fqnOrRefTypePartFirst(path: AstPath<import("java-parser").FqnOrRefTypePartFirstCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
fqnOrRefTypePartRest(path: AstPath<import("java-parser").FqnOrRefTypePartRestCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
fqnOrRefTypePartCommon(path: AstPath<import("java-parser").FqnOrRefTypePartCommonCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc;
parenthesisExpression(path: AstPath<import("java-parser").ParenthesisExpressionCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Group | "()" | (string | builders.Indent)[];
castExpression: typeof printSingle;
primitiveCastExpression(path: AstPath<import("java-parser").PrimitiveCastExpressionCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
referenceTypeCastExpression(path: AstPath<import("java-parser").ReferenceTypeCastExpressionCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
newExpression: typeof printSingle;
unqualifiedClassInstanceCreationExpression(path: AstPath<import("java-parser").UnqualifiedClassInstanceCreationExpressionCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
classOrInterfaceTypeToInstantiate(path: AstPath<import("java-parser").ClassOrInterfaceTypeToInstantiateCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
typeArgumentsOrDiamond: typeof printSingle;
diamond(): string;
methodInvocationSuffix(path: AstPath<import("java-parser").MethodInvocationSuffixCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Group | "()";
argumentList(path: AstPath<import("java-parser").ArgumentListCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Group | (builders.Indent | builders.Softline)[] | (builders.BreakParent | builders.Group)[];
arrayCreationExpression(path: AstPath<import("java-parser").ArrayCreationExpressionCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
arrayCreationExpressionWithoutInitializerSuffix(path: AstPath<import("java-parser").ArrayCreationExpressionWithoutInitializerSuffixCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc;
arrayCreationWithInitializerSuffix(path: AstPath<import("java-parser").ArrayCreationWithInitializerSuffixCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
dimExprs(path: AstPath<import("java-parser").DimExprsCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
dimExpr(path: AstPath<import("java-parser").DimExprCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
classLiteralSuffix(path: AstPath<import("java-parser").ClassLiteralSuffixCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
arrayAccessSuffix(path: AstPath<import("java-parser").ArrayAccessSuffixCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
methodReferenceSuffix(path: AstPath<import("java-parser").MethodReferenceSuffixCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
templateArgument: typeof printSingle;
template: typeof printSingle;
stringTemplate(path: AstPath<StringTemplateCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Indent;
textBlockTemplate(path: AstPath<TextBlockTemplateCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Indent;
embeddedExpression: typeof printSingle;
pattern: typeof printSingle;
typePattern: typeof printSingle;
recordPattern(path: AstPath<import("java-parser").RecordPatternCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
componentPatternList(path: AstPath<import("java-parser").ComponentPatternListCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
componentPattern: typeof printSingle;
matchAllPattern: typeof printSingle;
guard(path: AstPath<import("java-parser").GuardCstNode & {
comments?: JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
};
export default _default;

View File

@@ -0,0 +1,598 @@
import { builders, utils } from "prettier/doc";
import { call, definedKeys, each, findBaseIndent, flatMap, hasLeadingComments, indentInParentheses, isBinaryExpression, isNonTerminal, isTerminal, map, onlyDefinedKey, printDanglingComments, printList, printName, printSingle } from "./helpers.js";
const { breakParent, conditionalGroup, group, hardline, ifBreak, indent, indentIfBreak, join, line, lineSuffixBoundary, softline } = builders;
const { removeLines, willBreak } = utils;
export default {
expression: printSingle,
lambdaExpression(path, print, _, args = {}) {
var _a;
const hug = (_a = args.hug) !== null && _a !== void 0 ? _a : false;
const parameters = call(path, print, "lambdaParameters");
const expression = [hug ? removeLines(parameters) : parameters, " ->"];
const lambdaExpression = path.node.children.lambdaBody[0].children.expression;
const body = call(path, print, "lambdaBody");
if (lambdaExpression) {
const suffix = indent([line, body]);
expression.push(group(hug ? [suffix, softline] : suffix));
}
else {
expression.push(" ", body);
}
return expression;
},
lambdaParameters(path, print, options) {
const parameters = printSingle(path, print);
return !path.node.children.lambdaParametersWithBraces &&
options.arrowParens === "always"
? ["(", parameters, ")"]
: parameters;
},
lambdaParametersWithBraces(path, print, options) {
var _a;
const { lambdaParameterList } = path.node.children;
if (!lambdaParameterList) {
return "()";
}
const { conciseLambdaParameterList, normalLambdaParameterList } = lambdaParameterList[0].children;
const parameterCount = ((_a = conciseLambdaParameterList === null || conciseLambdaParameterList === void 0 ? void 0 : conciseLambdaParameterList[0].children.conciseLambdaParameter) !== null && _a !== void 0 ? _a : normalLambdaParameterList === null || normalLambdaParameterList === void 0 ? void 0 : normalLambdaParameterList[0].children.normalLambdaParameter).length;
const parameters = call(path, print, "lambdaParameterList");
if (parameterCount > 1) {
return indentInParentheses(parameters);
}
return conciseLambdaParameterList && options.arrowParens === "avoid"
? parameters
: ["(", parameters, ")"];
},
lambdaParameterList: printSingle,
conciseLambdaParameterList(path, print) {
return printList(path, print, "conciseLambdaParameter");
},
normalLambdaParameterList(path, print) {
return printList(path, print, "normalLambdaParameter");
},
normalLambdaParameter: printSingle,
regularLambdaParameter(path, print) {
return join(" ", [
...map(path, print, "variableModifier"),
call(path, print, "lambdaParameterType"),
call(path, print, "variableDeclaratorId")
]);
},
lambdaParameterType: printSingle,
conciseLambdaParameter: printSingle,
lambdaBody: printSingle,
conditionalExpression(path, print) {
var _a;
const binaryExpression = call(path, print, "binaryExpression");
if (!path.node.children.QuestionMark) {
return binaryExpression;
}
const expressions = map(path, print, "expression");
const contents = indent(join(line, [
binaryExpression,
["? ", expressions[0]],
[": ", expressions[1]]
]));
const isNestedTernary = ((_a = path.getNode(4)) === null || _a === void 0 ? void 0 : _a.name) ===
"conditionalExpression";
return isNestedTernary ? contents : group(contents);
},
binaryExpression(path, print, options) {
var _a, _b;
const { children } = path.node;
const operands = flatMap(path, print, definedKeys(children, [
"expression",
"pattern",
"referenceType",
"unaryExpression"
]));
const operators = flatMap(path, operatorPath => {
const { node } = operatorPath;
let image;
if (isTerminal(node)) {
image = node.image;
}
else if (node.children.Less) {
image = "<<";
}
else {
image = node.children.Greater.length === 2 ? ">>" : ">>>";
}
return { image, doc: print(operatorPath) };
}, definedKeys(children, [
"AssignmentOperator",
"BinaryOperator",
"Instanceof",
"shiftOperator"
]));
const hasNonAssignmentOperators = (operators.length > 0 && !children.AssignmentOperator) ||
(children.expression !== undefined &&
isBinaryExpression(children.expression[0]));
const isInList = ((_a = path.getNode(4)) === null || _a === void 0 ? void 0 : _a.name) === "elementValue" ||
((_b = path.getNode(6)) === null || _b === void 0 ? void 0 : _b.name) === "argumentList";
return binary(operands, operators, {
hasNonAssignmentOperators,
isInList,
isRoot: true,
operatorPosition: options.experimentalOperatorPosition
});
},
unaryExpression(path, print) {
return [
...map(path, print, "UnaryPrefixOperator"),
call(path, print, "primary"),
...map(path, print, "UnarySuffixOperator")
];
},
unaryExpressionNotPlusMinus(path, print) {
const { children } = path.node;
const expression = [];
if (children.UnaryPrefixOperatorNotPlusMinus) {
expression.push(...map(path, print, "UnaryPrefixOperatorNotPlusMinus"));
}
expression.push(call(path, print, "primary"));
if (children.UnarySuffixOperator) {
expression.push(...map(path, print, "UnarySuffixOperator"));
}
return join(" ", expression);
},
primary(path, print) {
var _a, _b;
const { children } = path.node;
if (!children.primarySuffix) {
return call(path, print, "primaryPrefix");
}
const methodInvocations = children.primarySuffix
.filter(({ children }) => children.methodInvocationSuffix)
.map(({ children }) => children.methodInvocationSuffix[0].children);
const hasLambdaMethodParameter = methodInvocations.some(({ argumentList }) => argumentList === null || argumentList === void 0 ? void 0 : argumentList[0].children.expression.some(({ children }) => children.lambdaExpression));
const prefixIsCallExpression = children.primaryPrefix[0].children.newExpression;
const callExpressionCount = methodInvocations.length +
(prefixIsCallExpression ? 1 : 0) +
children.primarySuffix.filter(({ children }) => children.unqualifiedClassInstanceCreationExpression).length;
const fqnOrRefType = (_a = children.primaryPrefix[0].children.fqnOrRefType) === null || _a === void 0 ? void 0 : _a[0].children;
const prefixIsMethodInvocation = (fqnOrRefType === null || fqnOrRefType === void 0 ? void 0 : fqnOrRefType.fqnOrRefTypePartRest) !== undefined &&
((_b = children.primarySuffix) === null || _b === void 0 ? void 0 : _b[0].children.methodInvocationSuffix) !== undefined;
const prefixIsStaticMethodInvocation = prefixIsMethodInvocation && isCapitalizedIdentifier(fqnOrRefType);
const prefixIsInstanceMethodInvocation = prefixIsMethodInvocation && !prefixIsStaticMethodInvocation;
const mustBreakForCallExpressions = methodInvocations.length > 2 && hasLambdaMethodParameter;
const separator = mustBreakForCallExpressions ? hardline : softline;
const prefix = [
call(path, prefixPath => print(prefixPath, {
lastSeparator: prefixIsStaticMethodInvocation ||
(prefixIsInstanceMethodInvocation && callExpressionCount === 1)
? ""
: separator
}), "primaryPrefix")
];
const canBreakForCallExpressions = callExpressionCount > 2 ||
(callExpressionCount === 2 && prefixIsInstanceMethodInvocation) ||
willBreak(prefix);
const suffixes = [];
each(path, suffixPath => {
const { node, previous } = suffixPath;
const suffix = print(suffixPath);
if (node.children.Dot) {
if ((canBreakForCallExpressions &&
((!previous && prefixIsCallExpression) ||
(previous === null || previous === void 0 ? void 0 : previous.children.methodInvocationSuffix) ||
(previous === null || previous === void 0 ? void 0 : previous.children.unqualifiedClassInstanceCreationExpression))) ||
(!node.children.templateArgument && willBreak(suffix))) {
suffixes.push(separator);
}
suffixes.push(suffix);
}
else if (previous) {
suffixes.push(suffix);
}
else {
prefix.push(prefixIsInstanceMethodInvocation && callExpressionCount >= 2
? indent(suffix)
: suffix);
}
}, "primarySuffix");
const hasSuffixComments = children.primarySuffix.some(suffix => hasLeadingComments(suffix));
return group(canBreakForCallExpressions || hasSuffixComments
? [prefix, indent(suffixes)]
: [prefix, ...suffixes]);
},
primaryPrefix: printSingle,
primarySuffix(path, print) {
const { children } = path.node;
if (!children.Dot) {
return printSingle(path, print);
}
const suffix = ["."];
if (children.This) {
suffix.push("this");
}
else if (children.Identifier) {
if (children.typeArguments) {
suffix.push(call(path, print, "typeArguments"));
}
suffix.push(call(path, print, "Identifier"));
}
else {
const suffixKey = onlyDefinedKey(children, [
"templateArgument",
"unqualifiedClassInstanceCreationExpression"
]);
suffix.push(call(path, print, suffixKey));
}
return suffix;
},
fqnOrRefType(path, print, _, args) {
var _a;
const lastSeparator = (_a = args.lastSeparator) !== null && _a !== void 0 ? _a : "";
const fqnOrRefType = [
call(path, print, "fqnOrRefTypePartFirst"),
...map(path, partPath => {
const part = print(partPath);
return partPath.isLast
? [willBreak(part) ? hardline : lastSeparator, part]
: part;
}, "fqnOrRefTypePartRest")
];
fqnOrRefType.push(indent(fqnOrRefType.pop()));
return path.node.children.dims
? [fqnOrRefType, call(path, print, "dims")]
: fqnOrRefType;
},
fqnOrRefTypePartFirst(path, print) {
return join(" ", [
...map(path, print, "annotation"),
call(path, print, "fqnOrRefTypePartCommon")
]);
},
fqnOrRefTypePartRest(path, print) {
const common = call(path, print, "fqnOrRefTypePartCommon");
const type = path.node.children.typeArguments
? [call(path, print, "typeArguments"), common]
: common;
return [".", ...join(" ", [...map(path, print, "annotation"), type])];
},
fqnOrRefTypePartCommon(path, print) {
const { children } = path.node;
const keywordKey = onlyDefinedKey(children, ["Identifier", "Super"]);
const keyword = call(path, print, keywordKey);
return children.typeArguments
? [keyword, call(path, print, "typeArguments")]
: keyword;
},
parenthesisExpression(path, print) {
var _a;
const expression = call(path, print, "expression");
const ancestorName = (_a = path.getNode(14)) === null || _a === void 0 ? void 0 : _a.name;
const binaryExpression = path.getNode(8);
return ancestorName &&
["guard", "returnStatement"].includes(ancestorName) &&
binaryExpression &&
binaryExpression.name === "binaryExpression" &&
Object.keys(binaryExpression.children).length === 1
? indentInParentheses(expression)
: ["(", indent(expression), ")"];
},
castExpression: printSingle,
primitiveCastExpression(path, print) {
return [
"(",
call(path, print, "primitiveType"),
") ",
call(path, print, "unaryExpression")
];
},
referenceTypeCastExpression(path, print) {
const { children } = path.node;
const type = call(path, print, "referenceType");
const cast = children.additionalBound
? indentInParentheses(join(line, [type, ...map(path, print, "additionalBound")]))
: ["(", type, ")"];
const expressionKey = onlyDefinedKey(children, [
"lambdaExpression",
"unaryExpressionNotPlusMinus"
]);
return [cast, " ", call(path, print, expressionKey)];
},
newExpression: printSingle,
unqualifiedClassInstanceCreationExpression(path, print) {
const { children } = path.node;
const expression = ["new "];
if (children.typeArguments) {
expression.push(call(path, print, "typeArguments"));
}
expression.push(call(path, print, "classOrInterfaceTypeToInstantiate"), children.argumentList
? group(["(", call(path, print, "argumentList"), ")"])
: "()");
if (children.classBody) {
expression.push(" ", call(path, print, "classBody"));
}
return expression;
},
classOrInterfaceTypeToInstantiate(path, print) {
const { children } = path.node;
const type = children.annotation
? flatMap(path, childPath => [
print(childPath),
isNonTerminal(childPath.node) ? " " : "."
], ["annotation", "Identifier"])
: printName(path, print);
if (children.typeArgumentsOrDiamond) {
type.push(call(path, print, "typeArgumentsOrDiamond"));
}
return type;
},
typeArgumentsOrDiamond: printSingle,
diamond() {
return "<>";
},
methodInvocationSuffix(path, print) {
return path.node.children.argumentList
? group(["(", call(path, print, "argumentList"), ")"])
: indentInParentheses(printDanglingComments(path), { shouldBreak: true });
},
argumentList(path, print) {
var _a, _b, _c, _d;
const expressions = path.node.children.expression;
const lastExpression = expressions.at(-1);
const lastExpressionLambdaBodyExpression = (_b = (_a = lastExpression.children.lambdaExpression) === null || _a === void 0 ? void 0 : _a[0].children.lambdaBody[0].children.expression) === null || _b === void 0 ? void 0 : _b[0].children;
const lastExpressionLambdaBodyTernaryExpression = (_c = lastExpressionLambdaBodyExpression === null || lastExpressionLambdaBodyExpression === void 0 ? void 0 : lastExpressionLambdaBodyExpression.conditionalExpression) === null || _c === void 0 ? void 0 : _c[0].children;
const isHuggable = !lastExpression.comments &&
(!lastExpressionLambdaBodyExpression ||
(lastExpressionLambdaBodyTernaryExpression === null || lastExpressionLambdaBodyTernaryExpression === void 0 ? void 0 : lastExpressionLambdaBodyTernaryExpression.QuestionMark) !== undefined ||
((_d = lastExpressionLambdaBodyTernaryExpression === null || lastExpressionLambdaBodyTernaryExpression === void 0 ? void 0 : lastExpressionLambdaBodyTernaryExpression.binaryExpression) === null || _d === void 0 ? void 0 : _d[0].children.unaryExpression.length) === 1) &&
expressions.findIndex(({ children }) => children.lambdaExpression) ===
expressions.length - 1;
const args = map(path, print, "expression");
const allArgsExpandable = [
indent([softline, ...join([",", line], args)]),
softline
];
if (!isHuggable || willBreak(args.at(-1)[0])) {
return allArgsExpandable;
}
const headArgs = args.slice(0, -1);
const huggedLastArg = path.call(argPath => print(argPath, { hug: true }), "children", "expression", args.length - 1);
const lastArgExpanded = join(", ", [
...headArgs,
group(huggedLastArg, { shouldBreak: true })
]);
if (willBreak(huggedLastArg)) {
return [
breakParent,
conditionalGroup([lastArgExpanded, allArgsExpandable])
];
}
return conditionalGroup([
join(", ", [...headArgs, huggedLastArg]),
lastArgExpanded,
allArgsExpandable
]);
},
arrayCreationExpression(path, print) {
const { children } = path.node;
const typeKey = onlyDefinedKey(children, [
"classOrInterfaceType",
"primitiveType"
]);
const suffixKey = onlyDefinedKey(children, [
"arrayCreationExpressionWithoutInitializerSuffix",
"arrayCreationWithInitializerSuffix"
]);
return ["new ", call(path, print, typeKey), call(path, print, suffixKey)];
},
arrayCreationExpressionWithoutInitializerSuffix(path, print) {
const expressions = call(path, print, "dimExprs");
return path.node.children.dims
? [expressions, call(path, print, "dims")]
: expressions;
},
arrayCreationWithInitializerSuffix(path, print) {
return [
call(path, print, "dims"),
" ",
call(path, print, "arrayInitializer")
];
},
dimExprs(path, print) {
return map(path, print, "dimExpr");
},
dimExpr(path, print) {
return join(" ", [
...map(path, print, "annotation"),
["[", call(path, print, "expression"), "]"]
]);
},
classLiteralSuffix(path, print) {
const lSquares = map(path, print, "LSquare");
const rSquares = map(path, print, "RSquare");
return [
...lSquares.flatMap((lSquare, index) => [lSquare, rSquares[index]]),
".class"
];
},
arrayAccessSuffix(path, print) {
return ["[", call(path, print, "expression"), "]"];
},
methodReferenceSuffix(path, print) {
const { children } = path.node;
const reference = ["::"];
if (children.typeArguments) {
reference.push(call(path, print, "typeArguments"));
}
reference.push(call(path, print, onlyDefinedKey(children, ["Identifier", "New"])));
return reference;
},
templateArgument: printSingle,
template: printSingle,
stringTemplate(path, print) {
return printTemplate(path, print, "StringTemplateBegin", "StringTemplateMid", "StringTemplateEnd");
},
textBlockTemplate(path, print) {
return printTemplate(path, print, "TextBlockTemplateBegin", "TextBlockTemplateMid", "TextBlockTemplateEnd");
},
embeddedExpression: printSingle,
pattern: printSingle,
typePattern: printSingle,
recordPattern(path, print) {
const patterns = path.node.children.componentPatternList
? indentInParentheses(call(path, print, "componentPatternList"))
: "()";
return [call(path, print, "referenceType"), patterns];
},
componentPatternList(path, print) {
return printList(path, print, "componentPattern");
},
componentPattern: printSingle,
matchAllPattern: printSingle,
guard(path, print) {
var _a;
const expression = call(path, print, "expression");
const hasParentheses = ((_a = path.node.children.expression[0].children.conditionalExpression) === null || _a === void 0 ? void 0 : _a[0].children.binaryExpression[0].children.unaryExpression[0].children.primary[0].children.primaryPrefix[0].children.parenthesisExpression) !==
undefined;
return [
"when ",
hasParentheses
? expression
: group([
ifBreak("("),
indent([softline, expression]),
softline,
ifBreak(")")
])
];
}
};
function binary(operands, operators, { hasNonAssignmentOperators = false, isInList = false, isRoot = false, operatorPosition }) {
let levelOperator;
let levelPrecedence;
let level = [];
while (operators.length) {
const nextOperator = operators[0].image;
const nextPrecedence = getOperatorPrecedence(nextOperator);
if (levelPrecedence === undefined || nextPrecedence === levelPrecedence) {
const { image: operator, doc: operatorDoc } = operators.shift();
level.push(operands.shift());
if (levelOperator !== undefined &&
needsParentheses(levelOperator, operator)) {
level = [["(", group(indent(level)), ")"]];
}
const parts = [" ", operatorDoc, line];
if (operatorPosition === "start" && !isAssignmentOperator(operator)) {
parts.reverse();
}
level.push(parts);
levelOperator = operator;
levelPrecedence = nextPrecedence;
}
else if (nextPrecedence < levelPrecedence) {
if (!isRoot) {
break;
}
level.push(operands.shift());
const content = group(indent(level));
operands.unshift(levelOperator !== undefined &&
needsParentheses(levelOperator, nextOperator)
? ["(", content, ")"]
: content);
level = [];
levelOperator = undefined;
levelPrecedence = undefined;
}
else {
const content = binary(operands, operators, { operatorPosition });
operands.unshift(levelOperator !== undefined &&
needsParentheses(nextOperator, levelOperator)
? ["(", indent(content), ")"]
: content);
}
}
level.push(operands.shift());
if (!levelOperator ||
(!isInList &&
!isAssignmentOperator(levelOperator) &&
levelOperator !== "instanceof")) {
return group(level);
}
if (!isRoot || hasNonAssignmentOperators) {
return group(indent(level));
}
const groupId = Symbol("assignment");
return group([
level[0],
group(indent(level[1]), { id: groupId }),
indentIfBreak(level[2], { groupId })
]);
}
const precedencesByOperator = new Map([
["||"],
["&&"],
["|"],
["^"],
["&"],
["==", "!="],
["<", ">", "<=", ">=", "instanceof"],
["<<", ">>", ">>>"],
["+", "-"],
["*", "/", "%"]
].flatMap((operators, index) => operators.map(operator => [operator, index])));
function getOperatorPrecedence(operator) {
var _a;
return (_a = precedencesByOperator.get(operator)) !== null && _a !== void 0 ? _a : -1;
}
function needsParentheses(operator, parentOperator) {
return ((operator === "&&" && parentOperator === "||") ||
(["|", "^", "&", "<<", ">>", ">>>"].includes(parentOperator) &&
getOperatorPrecedence(operator) >
getOperatorPrecedence(parentOperator)) ||
[operator, parentOperator].every(o => ["==", "!="].includes(o)) ||
[operator, parentOperator].every(o => ["<<", ">>", ">>>"].includes(o)) ||
(operator === "*" && parentOperator === "/") ||
(operator === "/" && parentOperator === "*") ||
(operator === "%" && ["+", "-", "*", "/"].includes(parentOperator)) ||
(["*", "/"].includes(operator) && parentOperator === "%"));
}
const assignmentOperators = new Set([
"=",
"*=",
"/=",
"%=",
"+=",
"-=",
"<<=",
">>=",
">>>=",
"&=",
"^=",
"|="
]);
function isAssignmentOperator(operator) {
return assignmentOperators.has(operator);
}
function isCapitalizedIdentifier(fqnOrRefType) {
var _a, _b, _c;
const nextToLastIdentifier = (_c = (_b = [
fqnOrRefType.fqnOrRefTypePartFirst[0],
...((_a = fqnOrRefType.fqnOrRefTypePartRest) !== null && _a !== void 0 ? _a : [])
].at(-2)) === null || _b === void 0 ? void 0 : _b.children.fqnOrRefTypePartCommon[0].children.Identifier) === null || _c === void 0 ? void 0 : _c[0].image;
return /^\p{Uppercase_Letter}/u.test(nextToLastIdentifier !== null && nextToLastIdentifier !== void 0 ? nextToLastIdentifier : "");
}
function printTemplate(path, print, beginKey, midKey, endKey) {
const begin = call(path, ({ node }) => node.image, beginKey);
const mids = map(path, ({ node }) => node.image, midKey);
const end = call(path, ({ node }) => node.image, endKey);
const lines = [begin, ...mids, end].join("").split("\n").slice(1);
const baseIndent = findBaseIndent(lines);
const prefix = "\n" + " ".repeat(baseIndent);
const parts = [begin, ...mids, end].map(image => join(hardline, image.split(prefix)));
return indent([
parts[0],
...map(path, (expressionPath, index) => {
const expression = group([
indent([softline, print(expressionPath), lineSuffixBoundary]),
softline
]);
return index === 0 ? expression : [parts[index], expression];
}, "embeddedExpression"),
parts.at(-1)
]);
}

View File

@@ -0,0 +1,71 @@
import type { AnnotationCstNode, ClassPermitsCstNode, ClassTypeCtx, CstElement, CstNode, ExpressionCstNode, InterfacePermitsCstNode, IToken, StatementCstNode } from "java-parser";
import type { AstPath, Doc, ParserOptions } from "prettier";
import { builders } from "prettier/doc";
import type { JavaComment } from "../comments.js";
export declare function onlyDefinedKey<T extends Record<string, any>, K extends Key<T> & string>(obj: T, options?: K[]): K;
export declare function definedKeys<T extends Record<string, any>, K extends Key<T> & string>(obj: T, options?: K[]): K[];
export declare function printWithModifiers<T extends CstNode, P extends IterProperties<T["children"]>>(path: AstPath<T>, print: JavaPrintFn, modifierChild: P, contents: Doc, noTypeAnnotations?: boolean): builders.Doc[];
export declare function hasDeclarationAnnotations(modifiers: ModifierNode[]): boolean;
export declare function call<T extends CstNode, U, P extends IterProperties<T["children"]>>(path: AstPath<T>, callback: MapCallback<IndexValue<IndexValue<T, "children">, P>, U>, child: P): U;
export declare function each<T extends CstNode, P extends IterProperties<T["children"]>>(path: AstPath<T>, callback: MapCallback<IndexValue<IndexValue<T, "children">, P>, void>, child: P): void;
export declare function map<T extends CstNode, U, P extends IterProperties<T["children"]>>(path: AstPath<T>, callback: MapCallback<IndexValue<IndexValue<T, "children">, P>, U>, child: P): U[];
export declare function flatMap<T extends CstNode, U, P extends IterProperties<T["children"]>>(path: AstPath<T>, callback: MapCallback<IndexValue<IndexValue<T, "children">, P>, U>, children: P[]): U[];
export declare function printSingle(path: AstPath<JavaNonTerminal>, print: JavaPrintFn, _?: JavaParserOptions, args?: unknown): builders.Doc;
export declare function lineStartWithComments(node: JavaNonTerminal): number;
export declare function lineEndWithComments(node: JavaNonTerminal): number;
export declare function printDanglingComments(path: AstPath<JavaNonTerminal>): builders.Doc[];
export declare function printComment(node: JavaTerminal): string | builders.Doc[];
export declare function hasLeadingComments(node: JavaNode): boolean | undefined;
export declare function indentInParentheses(contents: Doc, opts?: {
shouldBreak?: boolean;
}): builders.Group | "()";
export declare function printArrayInitializer<T extends JavaNonTerminal, P extends IterProperties<T["children"]>>(path: AstPath<T>, print: JavaPrintFn, options: JavaParserOptions, child: P): builders.Group | "{}";
export declare function printBlock(path: AstPath<JavaNonTerminal>, contents: Doc[]): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
export declare function printName(path: AstPath<JavaNonTerminal & {
children: {
Identifier: IToken[];
};
}>, print: JavaPrintFn): builders.Doc[];
export declare function printList<T extends JavaNonTerminal, P extends IterProperties<T["children"]>>(path: AstPath<T>, print: JavaPrintFn, child: P): builders.Doc[];
export declare function printClassPermits(path: AstPath<ClassPermitsCstNode | InterfacePermitsCstNode>, print: JavaPrintFn): builders.Group;
export declare function printClassType(path: AstPath<JavaNonTerminal & {
children: ClassTypeCtx;
}>, print: JavaPrintFn): builders.Doc[];
export declare function isBinaryExpression(expression: ExpressionCstNode): boolean;
export declare function findBaseIndent(lines: string[]): number;
export declare function isEmptyStatement(statement: StatementCstNode): boolean;
export declare function isNonTerminal(node: CstElement): node is JavaNonTerminal;
export declare function isTerminal(node: CstElement): node is IToken;
export type JavaNode = CstElement & {
comments?: JavaComment[];
};
export type JavaNonTerminal = Exclude<JavaNode, IToken>;
export type JavaTerminal = Exclude<JavaNode, CstNode>;
export type JavaNodePrinters = {
[T in JavaNonTerminal["name"]]: JavaNodePrinter<T>;
};
export type JavaNodePrinter<T> = (path: AstPath<Extract<JavaNonTerminal, {
name: T;
}>>, print: JavaPrintFn, options: JavaParserOptions, args?: unknown) => Doc;
export type JavaPrintFn = (path: AstPath<JavaNode>, args?: unknown) => Doc;
export type JavaParserOptions = ParserOptions<JavaNode> & {
entrypoint?: string;
};
export type IterProperties<T> = T extends any[] ? IndexProperties<T> : ArrayProperties<T>;
type Key<T> = T extends T ? keyof T : never;
type ModifierNode = JavaNonTerminal & {
children: {
annotation?: AnnotationCstNode[];
};
};
type IsTuple<T> = T extends [] ? true : T extends [infer First, ...infer Remain] ? IsTuple<Remain> : false;
type IndexProperties<T extends {
length: number;
}> = IsTuple<T> extends true ? Exclude<Partial<T>["length"], T["length"]> : number;
type ArrayProperties<T> = {
[K in keyof T]: NonNullable<T[K]> extends readonly any[] ? K : never;
}[keyof T];
type ArrayElement<T> = T extends Array<infer E> ? E : never;
type MapCallback<T, U> = (path: AstPath<ArrayElement<T>>, index: number, value: any) => U;
type IndexValue<T, P> = T extends any[] ? P extends number ? T[P] : never : P extends keyof T ? T[P] : never;
export {};

View File

@@ -0,0 +1,239 @@
import { builders } from "prettier/doc";
import parser from "../parser.js";
const { group, hardline, ifBreak, indent, join, line, softline } = builders;
export function onlyDefinedKey(obj, options) {
const keys = definedKeys(obj, options);
if (keys.length === 1) {
return keys[0];
}
throw new Error(keys.length > 1
? `More than one defined key found: ${keys}`
: "No defined keys found");
}
export function definedKeys(obj, options) {
return (options !== null && options !== void 0 ? options : Object.keys(obj)).filter(key => obj[key] !== undefined);
}
const indexByModifier = [
"public",
"protected",
"private",
"abstract",
"default",
"static",
"final",
"transient",
"volatile",
"synchronized",
"native",
"sealed",
"non-sealed",
"strictfp"
].reduce((map, name, index) => map.set(name, index), new Map());
export function printWithModifiers(path, print, modifierChild, contents, noTypeAnnotations = false) {
const declarationAnnotations = [];
const otherModifiers = [];
const typeAnnotations = [];
each(path, modifierPath => {
const { children } = modifierPath.node;
const modifier = print(modifierPath);
if (children.annotation) {
(otherModifiers.length ? typeAnnotations : declarationAnnotations).push(modifier);
}
else {
otherModifiers.push(modifier);
declarationAnnotations.push(...typeAnnotations);
typeAnnotations.length = 0;
}
}, modifierChild);
if (noTypeAnnotations) {
declarationAnnotations.push(...typeAnnotations);
typeAnnotations.length = 0;
}
otherModifiers.sort((a, b) => indexByModifier.get(a) - indexByModifier.get(b));
return join(hardline, [
...declarationAnnotations,
join(" ", [...otherModifiers, ...typeAnnotations, contents])
]);
}
export function hasDeclarationAnnotations(modifiers) {
let hasAnnotation = false;
let hasNonAnnotation = false;
for (const modifier of modifiers) {
if (modifier.children.annotation) {
hasAnnotation = true;
}
else if (hasAnnotation) {
return true;
}
else {
hasNonAnnotation = true;
}
}
return hasAnnotation && !hasNonAnnotation;
}
export function call(path, callback, child) {
return path.map(callback, "children", child)[0];
}
export function each(path, callback, child) {
if (path.node.children[child]) {
path.each(callback, "children", child);
}
}
export function map(path, callback, child) {
return path.node.children[child] ? path.map(callback, "children", child) : [];
}
export function flatMap(path, callback, children) {
return children
.flatMap(child => map(path, callback, child).map((doc, index) => {
const node = path.node.children[child][index];
return {
doc,
startOffset: parser.locStart(node)
};
}))
.sort((a, b) => a.startOffset - b.startOffset)
.map(({ doc }) => doc);
}
export function printSingle(path, print, _, args) {
return call(path, childPath => print(childPath, args), onlyDefinedKey(path.node.children));
}
export function lineStartWithComments(node) {
const { comments, location } = node;
return comments
? Math.min(location.startLine, comments[0].startLine)
: location.startLine;
}
export function lineEndWithComments(node) {
const { comments, location } = node;
return comments
? Math.max(location.endLine, comments.at(-1).endLine)
: location.endLine;
}
export function printDanglingComments(path) {
if (!path.node.comments) {
return [];
}
const comments = [];
path.each(commentPath => {
const comment = commentPath.node;
if (comment.leading || comment.trailing) {
return;
}
comment.printed = true;
comments.push(printComment(comment));
}, "comments");
return join(hardline, comments);
}
export function printComment(node) {
const { image } = node;
const lines = image.split("\n").map(line => line.trim());
return lines.length > 1 &&
lines[0].startsWith("/*") &&
lines.slice(1).every(line => line.startsWith("*")) &&
lines.at(-1).endsWith("*/")
? join(hardline, lines.map((line, index) => (index === 0 ? line : ` ${line}`)))
: image;
}
export function hasLeadingComments(node) {
var _a;
return (_a = node.comments) === null || _a === void 0 ? void 0 : _a.some(({ leading }) => leading);
}
export function indentInParentheses(contents, opts) {
return !Array.isArray(contents) || contents.length
? group(["(", indent([softline, contents]), softline, ")"], opts)
: "()";
}
export function printArrayInitializer(path, print, options, child) {
const list = [];
if (child && child in path.node.children) {
list.push(call(path, print, child));
if (options.trailingComma !== "none") {
list.push(ifBreak(","));
}
}
list.push(...printDanglingComments(path));
return list.length ? group(["{", indent([line, ...list]), line, "}"]) : "{}";
}
export function printBlock(path, contents) {
if (!contents.length) {
const danglingComments = printDanglingComments(path);
return danglingComments.length
? ["{", indent([hardline, ...danglingComments]), hardline, "}"]
: "{}";
}
return group([
"{",
indent([hardline, ...join(hardline, contents)]),
hardline,
"}"
]);
}
export function printName(path, print) {
return join(".", map(path, print, "Identifier"));
}
export function printList(path, print, child) {
return join([",", line], map(path, print, child));
}
export function printClassPermits(path, print) {
return group([
"permits",
indent([line, group(printList(path, print, "typeName"))])
]);
}
export function printClassType(path, print) {
const { children } = path.node;
return definedKeys(children, ["annotation", "Identifier", "typeArguments"])
.flatMap(child => children[child].map((node, index) => ({
child,
index,
startOffset: parser.locStart(node)
})))
.sort((a, b) => a.startOffset - b.startOffset)
.flatMap(({ child, index: childIndex }, index, array) => {
const node = children[child][childIndex];
const next = array.at(index + 1);
const nextNode = next && children[next.child][next.index];
const docs = [path.call(print, "children", child, childIndex)];
if (nextNode) {
if (isNonTerminal(node)) {
docs.push(node.name === "annotation" ? " " : ".");
}
else if (isTerminal(nextNode) || nextNode.name === "annotation") {
docs.push(".");
}
}
return docs;
});
}
export function isBinaryExpression(expression) {
var _a;
const conditionalExpression = (_a = expression.children.conditionalExpression) === null || _a === void 0 ? void 0 : _a[0].children;
if (!conditionalExpression) {
return false;
}
const isTernary = conditionalExpression.QuestionMark !== undefined;
if (isTernary) {
return false;
}
const hasNonAssignmentOperators = Object.values(conditionalExpression.binaryExpression[0].children).some(child => {
var _a;
return isTerminal(child[0]) &&
!((_a = child[0].tokenType.CATEGORIES) === null || _a === void 0 ? void 0 : _a.some(category => category.name === "AssignmentOperator"));
});
return hasNonAssignmentOperators;
}
export function findBaseIndent(lines) {
return lines.length
? Math.min(...lines.map(line => line.search(/\S/)).filter(indent => indent >= 0))
: 0;
}
export function isEmptyStatement(statement) {
var _a;
return (((_a = statement.children.statementWithoutTrailingSubstatement) === null || _a === void 0 ? void 0 : _a[0].children.emptyStatement) !== undefined);
}
export function isNonTerminal(node) {
return !isTerminal(node);
}
export function isTerminal(node) {
return "tokenType" in node;
}

View File

@@ -0,0 +1,2 @@
import type { JavaNodePrinter, JavaNodePrinters } from "./helpers.js";
export declare function printerForNodeType<T extends keyof JavaNodePrinters>(type: T): JavaNodePrinter<T>;

View File

@@ -0,0 +1,13 @@
import arrays from "./arrays.js";
import blocksAndStatements from "./blocks-and-statements.js";
import classes from "./classes.js";
import expressions from "./expressions.js";
import interfaces from "./interfaces.js";
import lexicalStructure from "./lexical-structure.js";
import names from "./names.js";
import packagesAndModules from "./packages-and-modules.js";
import typesValuesAndVariables from "./types-values-and-variables.js";
const printersByNodeType = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, arrays), blocksAndStatements), classes), expressions), interfaces), lexicalStructure), names), packagesAndModules), typesValuesAndVariables);
export function printerForNodeType(type) {
return printersByNodeType[type];
}

View File

@@ -0,0 +1,62 @@
import { builders } from "prettier/doc";
import { printClassPermits, printSingle } from "./helpers.js";
declare const _default: {
interfaceDeclaration(path: import("prettier").AstPath<import("java-parser").InterfaceDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
normalInterfaceDeclaration(path: import("prettier").AstPath<import("java-parser").NormalInterfaceDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
interfaceModifier: typeof printSingle;
interfaceExtends(path: import("prettier").AstPath<import("java-parser").InterfaceExtendsCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Group;
interfacePermits: typeof printClassPermits;
interfaceBody(path: import("prettier").AstPath<import("java-parser").InterfaceBodyCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
interfaceMemberDeclaration(path: import("prettier").AstPath<import("java-parser").InterfaceMemberDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc;
constantDeclaration(path: import("prettier").AstPath<import("java-parser").ConstantDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
constantModifier: typeof printSingle;
interfaceMethodDeclaration(path: import("prettier").AstPath<import("java-parser").InterfaceMethodDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
interfaceMethodModifier: typeof printSingle;
annotationInterfaceDeclaration(path: import("prettier").AstPath<import("java-parser").AnnotationInterfaceDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
annotationInterfaceBody(path: import("prettier").AstPath<import("java-parser").AnnotationInterfaceBodyCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
annotationInterfaceMemberDeclaration(path: import("prettier").AstPath<import("java-parser").AnnotationInterfaceMemberDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc;
annotationInterfaceElementDeclaration(path: import("prettier").AstPath<import("java-parser").AnnotationInterfaceElementDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
annotationInterfaceElementModifier: typeof printSingle;
defaultValue(path: import("prettier").AstPath<import("java-parser").DefaultValueCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
annotation(path: import("prettier").AstPath<import("java-parser").AnnotationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
elementValuePairList(path: import("prettier").AstPath<import("java-parser").ElementValuePairListCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
elementValuePair(path: import("prettier").AstPath<import("java-parser").ElementValuePairCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
elementValue: typeof printSingle;
elementValueArrayInitializer(path: import("prettier").AstPath<import("java-parser").ElementValueArrayInitializerCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn, options: import("./helpers.js").JavaParserOptions): builders.Group | "{}";
elementValueList(path: import("prettier").AstPath<import("java-parser").ElementValueListCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Group;
};
export default _default;

View File

@@ -0,0 +1,157 @@
import { builders } from "prettier/doc";
import { call, each, hasDeclarationAnnotations, indentInParentheses, lineEndWithComments, lineStartWithComments, onlyDefinedKey, printArrayInitializer, printBlock, printClassPermits, printList, printSingle, printWithModifiers } from "./helpers.js";
const { group, hardline, indent, join, line } = builders;
export default {
interfaceDeclaration(path, print) {
const declarationKey = onlyDefinedKey(path.node.children, [
"annotationInterfaceDeclaration",
"normalInterfaceDeclaration"
]);
return printWithModifiers(path, print, "interfaceModifier", call(path, print, declarationKey), true);
},
normalInterfaceDeclaration(path, print) {
const { interfaceExtends, interfacePermits, typeParameters } = path.node.children;
const header = ["interface ", call(path, print, "typeIdentifier")];
if (typeParameters) {
header.push(call(path, print, "typeParameters"));
}
if (interfaceExtends) {
header.push(indent([line, call(path, print, "interfaceExtends")]));
}
if (interfacePermits) {
header.push(indent([line, call(path, print, "interfacePermits")]));
}
return [group(header), " ", call(path, print, "interfaceBody")];
},
interfaceModifier: printSingle,
interfaceExtends(path, print) {
return group([
"extends",
indent([line, call(path, print, "interfaceTypeList")])
]);
},
interfacePermits: printClassPermits,
interfaceBody(path, print) {
const declarations = [];
let previousRequiresPadding = false;
each(path, declarationPath => {
var _a, _b, _c, _d;
const declaration = print(declarationPath);
if (declaration === "") {
return;
}
const { node, previous } = declarationPath;
const constantDeclaration = (_a = node.children.constantDeclaration) === null || _a === void 0 ? void 0 : _a[0].children;
const methodDeclaration = (_b = node.children.interfaceMethodDeclaration) === null || _b === void 0 ? void 0 : _b[0].children;
const currentRequiresPadding = (!constantDeclaration && !methodDeclaration) ||
(methodDeclaration === null || methodDeclaration === void 0 ? void 0 : methodDeclaration.methodBody[0].children.block) !== undefined ||
hasDeclarationAnnotations((_d = (_c = constantDeclaration === null || constantDeclaration === void 0 ? void 0 : constantDeclaration.constantModifier) !== null && _c !== void 0 ? _c : methodDeclaration === null || methodDeclaration === void 0 ? void 0 : methodDeclaration.interfaceMethodModifier) !== null && _d !== void 0 ? _d : []);
const blankLine = declarations.length > 0 &&
(previousRequiresPadding ||
currentRequiresPadding ||
lineStartWithComments(node) > lineEndWithComments(previous) + 1);
declarations.push(blankLine ? [hardline, declaration] : declaration);
previousRequiresPadding = currentRequiresPadding;
}, "interfaceMemberDeclaration");
return printBlock(path, declarations);
},
interfaceMemberDeclaration(path, print) {
const { children } = path.node;
return children.Semicolon
? ""
: call(path, print, onlyDefinedKey(children));
},
constantDeclaration(path, print) {
const declaration = [
call(path, print, "unannType"),
" ",
call(path, print, "variableDeclaratorList"),
";"
];
return printWithModifiers(path, print, "constantModifier", declaration);
},
constantModifier: printSingle,
interfaceMethodDeclaration(path, print) {
const declaration = [
call(path, print, "methodHeader"),
path.node.children.methodBody[0].children.Semicolon ? "" : " ",
call(path, print, "methodBody")
];
return printWithModifiers(path, print, "interfaceMethodModifier", declaration);
},
interfaceMethodModifier: printSingle,
annotationInterfaceDeclaration(path, print) {
return join(" ", [
"@interface",
call(path, print, "typeIdentifier"),
call(path, print, "annotationInterfaceBody")
]);
},
annotationInterfaceBody(path, print) {
const declarations = [];
each(path, declarationPath => {
const declaration = print(declarationPath);
if (declaration === "") {
return;
}
declarations.push(declarationPath.isFirst ? declaration : [hardline, declaration]);
}, "annotationInterfaceMemberDeclaration");
return printBlock(path, declarations);
},
annotationInterfaceMemberDeclaration(path, print) {
const { children } = path.node;
return children.Semicolon
? ""
: call(path, print, onlyDefinedKey(children));
},
annotationInterfaceElementDeclaration(path, print) {
const { dims, defaultValue } = path.node.children;
const declaration = [
call(path, print, "unannType"),
" ",
call(path, print, "Identifier"),
"()"
];
if (dims) {
declaration.push(call(path, print, "dims"));
}
if (defaultValue) {
declaration.push(" ", call(path, print, "defaultValue"));
}
declaration.push(";");
return printWithModifiers(path, print, "annotationInterfaceElementModifier", declaration);
},
annotationInterfaceElementModifier: printSingle,
defaultValue(path, print) {
return ["default ", call(path, print, "elementValue")];
},
annotation(path, print) {
const { children } = path.node;
const annotation = ["@", call(path, print, "typeName")];
if (children.elementValue || children.elementValuePairList) {
const valuesKey = onlyDefinedKey(children, [
"elementValue",
"elementValuePairList"
]);
annotation.push(indentInParentheses(call(path, print, valuesKey)));
}
return annotation;
},
elementValuePairList(path, print) {
return printList(path, print, "elementValuePair");
},
elementValuePair(path, print) {
return join(" ", [
call(path, print, "Identifier"),
call(path, print, "Equals"),
call(path, print, "elementValue")
]);
},
elementValue: printSingle,
elementValueArrayInitializer(path, print, options) {
return printArrayInitializer(path, print, options, "elementValueList");
},
elementValueList(path, print) {
return group(printList(path, print, "elementValue"));
}
};

View File

@@ -0,0 +1,14 @@
import { builders } from "prettier/doc";
import { printSingle } from "./helpers.js";
declare const _default: {
literal(path: import("prettier").AstPath<import("java-parser").LiteralCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc;
integerLiteral: typeof printSingle;
floatingPointLiteral: typeof printSingle;
booleanLiteral: typeof printSingle;
shiftOperator(path: import("prettier").AstPath<import("java-parser").ShiftOperatorCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
};
export default _default;

View File

@@ -0,0 +1,29 @@
import { builders } from "prettier/doc";
import { findBaseIndent, map, onlyDefinedKey, printSingle } from "./helpers.js";
const { hardline, indent, join } = builders;
export default {
literal(path, print) {
const { TextBlock } = path.node.children;
if (!TextBlock) {
return printSingle(path, print);
}
const [open, ...lines] = TextBlock[0].image.split("\n");
const baseIndent = findBaseIndent(lines);
const textBlock = join(hardline, [
open,
...lines.map(line => line.slice(baseIndent))
]);
const ancestor = path.getNode(14);
return (ancestor === null || ancestor === void 0 ? void 0 : ancestor.name) === "variableInitializer" ||
((ancestor === null || ancestor === void 0 ? void 0 : ancestor.name) === "binaryExpression" &&
ancestor.children.AssignmentOperator)
? indent(textBlock)
: textBlock;
},
integerLiteral: printSingle,
floatingPointLiteral: printSingle,
booleanLiteral: printSingle,
shiftOperator(path, print) {
return map(path, print, onlyDefinedKey(path.node.children));
}
};

View File

@@ -0,0 +1,12 @@
import { printName, printSingle } from "./helpers.js";
declare const _default: {
typeIdentifier: typeof printSingle;
moduleName: typeof printName;
packageName: typeof printName;
typeName: typeof printName;
expressionName: typeof printName;
methodName: typeof printSingle;
packageOrTypeName: typeof printName;
ambiguousName: typeof printName;
};
export default _default;

View File

@@ -0,0 +1,11 @@
import { printName, printSingle } from "./helpers.js";
export default {
typeIdentifier: printSingle,
moduleName: printName,
packageName: printName,
typeName: printName,
expressionName: printName,
methodName: printSingle,
packageOrTypeName: printName,
ambiguousName: printName
};

View File

@@ -0,0 +1,46 @@
import type { ExportsModuleDirectiveCstNode, ImportDeclarationCstNode, OpensModuleDirectiveCstNode } from "java-parser";
import type { AstPath } from "prettier";
import { builders } from "prettier/doc";
import { printSingle, type JavaPrintFn } from "./helpers.js";
declare const _default: {
compilationUnit(path: AstPath<import("java-parser").CompilationUnitCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
ordinaryCompilationUnit(path: AstPath<import("java-parser").OrdinaryCompilationUnitCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
modularCompilationUnit(path: AstPath<import("java-parser").ModularCompilationUnitCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
packageDeclaration(path: AstPath<import("java-parser").PackageDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
packageModifier: typeof printSingle;
importDeclaration(path: AstPath<ImportDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc;
typeDeclaration(path: AstPath<import("java-parser").TypeDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc;
moduleDeclaration(path: AstPath<import("java-parser").ModuleDeclarationCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
moduleDirective: typeof printSingle;
requiresModuleDirective(path: AstPath<import("java-parser").RequiresModuleDirectiveCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
exportsModuleDirective(path: AstPath<ExportsModuleDirectiveCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
opensModuleDirective(path: AstPath<OpensModuleDirectiveCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
usesModuleDirective(path: AstPath<import("java-parser").UsesModuleDirectiveCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
providesModuleDirective(path: AstPath<import("java-parser").ProvidesModuleDirectiveCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: JavaPrintFn): builders.Doc[];
requiresModifier: typeof printSingle;
};
export default _default;

View File

@@ -0,0 +1,169 @@
import { builders } from "prettier/doc";
import { call, lineEndWithComments, lineStartWithComments, map, printBlock, printDanglingComments, printName, printSingle } from "./helpers.js";
const { group, hardline, indent, join, line } = builders;
export default {
compilationUnit(path, print) {
return [...printDanglingComments(path), printSingle(path, print), hardline];
},
ordinaryCompilationUnit(path, print) {
const { children } = path.node;
const declarations = [];
if (children.packageDeclaration) {
declarations.push(call(path, print, "packageDeclaration"));
}
if (children.importDeclaration) {
const staticCount = sortImports(children.importDeclaration);
const importDeclarations = map(path, print, "importDeclaration").filter(doc => doc !== "");
const staticDeclarations = importDeclarations.slice(0, staticCount);
const nonStaticDeclarations = importDeclarations.slice(staticCount);
declarations.push(...[staticDeclarations, nonStaticDeclarations]
.filter(({ length }) => length)
.map(declarations => join(hardline, declarations)));
}
if (children.typeDeclaration) {
declarations.push(...map(path, print, "typeDeclaration").filter(declaration => declaration !== ""));
}
return join([hardline, hardline], declarations);
},
modularCompilationUnit(path, print) {
const { children } = path.node;
const declarations = [];
if (children.importDeclaration) {
const staticCount = sortImports(children.importDeclaration);
const importDeclarations = map(path, print, "importDeclaration").filter(doc => doc !== "");
const staticDeclarations = importDeclarations.slice(0, staticCount);
const nonStaticDeclarations = importDeclarations.slice(staticCount);
declarations.push(...[staticDeclarations, nonStaticDeclarations]
.filter(({ length }) => length)
.map(declarations => join(hardline, declarations)));
}
declarations.push(call(path, print, "moduleDeclaration"));
return join([hardline, hardline], declarations);
},
packageDeclaration(path, print) {
return join(hardline, [
...map(path, print, "packageModifier"),
["package ", printName(path, print), ";"]
]);
},
packageModifier: printSingle,
importDeclaration(path, print) {
const { children } = path.node;
if (children.emptyStatement) {
return call(path, print, "emptyStatement");
}
const declaration = ["import "];
if (children.Static) {
declaration.push("static ");
}
declaration.push(call(path, print, "packageOrTypeName"));
if (children.Star) {
declaration.push(".*");
}
declaration.push(";");
return declaration;
},
typeDeclaration(path, print) {
return path.node.children.Semicolon ? "" : printSingle(path, print);
},
moduleDeclaration(path, print) {
const { annotation, Open } = path.node.children;
const prefix = [];
if (annotation) {
prefix.push(...map(path, print, "annotation"));
}
if (Open) {
prefix.push("open");
}
const declarations = map(path, declarationPath => {
const declaration = print(declarationPath);
const { node, previous } = declarationPath;
return !previous ||
lineStartWithComments(node) <= lineEndWithComments(previous) + 1
? declaration
: [hardline, declaration];
}, "moduleDirective");
return join(" ", [
...prefix,
"module",
printName(path, print),
printBlock(path, declarations)
]);
},
moduleDirective: printSingle,
requiresModuleDirective(path, print) {
return join(" ", [
"requires",
...map(path, print, "requiresModifier"),
[call(path, print, "moduleName"), ";"]
]);
},
exportsModuleDirective(path, print) {
return printToModuleNamesDirective(path, print, "exports");
},
opensModuleDirective(path, print) {
return printToModuleNamesDirective(path, print, "opens");
},
usesModuleDirective(path, print) {
return ["uses ", call(path, print, "typeName"), ";"];
},
providesModuleDirective(path, print) {
const [firstTypeName, ...restTypeNames] = map(path, print, "typeName");
return [
"provides ",
firstTypeName,
group(indent([
line,
group(indent(["with", line, ...join([",", line], restTypeNames)]))
])),
";"
];
},
requiresModifier: printSingle
};
function sortImports(importDeclarations) {
importDeclarations.sort(({ children: a }, { children: b }) => {
if (a.Static && !b.Static) {
return -1;
}
else if (b.Static && !a.Static) {
return 1;
}
if (!b.packageOrTypeName) {
if (a.packageOrTypeName) {
return -1;
}
return 0;
}
else if (!a.packageOrTypeName) {
return 1;
}
return compareFqn(a.packageOrTypeName[0], b.packageOrTypeName[0]);
});
return importDeclarations.reduce((staticCount, importDeclaration) => importDeclaration.children.Static ? staticCount + 1 : staticCount, 0);
}
function compareFqn(a, b) {
const identifiersA = a.children.Identifier;
const identifiersB = b.children.Identifier;
const minParts = Math.min(identifiersA.length, identifiersB.length);
for (let i = 0; i < minParts; i++) {
const imageA = identifiersA[i].image;
const imageB = identifiersB[i].image;
if (imageA < imageB) {
return -1;
}
else if (imageA > imageB) {
return 1;
}
}
return identifiersA.length - identifiersB.length;
}
function printToModuleNamesDirective(path, print, prefix) {
const directive = [prefix, " ", call(path, print, "packageName")];
if (path.node.children.moduleName) {
const moduleNames = join([",", line], map(path, print, "moduleName"));
directive.push(group(indent([line, group(indent(["to", line, ...moduleNames]))])));
}
directive.push(";");
return directive;
}

View File

@@ -0,0 +1,46 @@
import { builders } from "prettier/doc";
import { printClassType, printSingle } from "./helpers.js";
declare const _default: {
primitiveType(path: import("prettier").AstPath<import("java-parser").PrimitiveTypeCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
numericType: typeof printSingle;
integralType: typeof printSingle;
floatingPointType: typeof printSingle;
referenceType(path: import("prettier").AstPath<import("java-parser").ReferenceTypeCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
classOrInterfaceType: typeof printSingle;
classType: typeof printClassType;
interfaceType: typeof printSingle;
typeVariable(path: import("prettier").AstPath<import("java-parser").TypeVariableCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
dims(path: import("prettier").AstPath<import("java-parser").DimsCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
typeParameter(path: import("prettier").AstPath<import("java-parser").TypeParameterCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
typeParameterModifier: typeof printSingle;
typeBound(path: import("prettier").AstPath<import("java-parser").TypeBoundCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
additionalBound(path: import("prettier").AstPath<import("java-parser").AdditionalBoundCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
typeArguments(path: import("prettier").AstPath<import("java-parser").TypeArgumentsCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Group;
typeArgumentList(path: import("prettier").AstPath<import("java-parser").TypeArgumentListCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
typeArgument: typeof printSingle;
wildcard(path: import("prettier").AstPath<import("java-parser").WildcardCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
wildcardBounds(path: import("prettier").AstPath<import("java-parser").WildcardBoundsCstNode & {
comments?: import("../comments.js").JavaComment[];
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
};
export default _default;

View File

@@ -0,0 +1,90 @@
import { builders } from "prettier/doc";
import { call, definedKeys, flatMap, isNonTerminal, map, onlyDefinedKey, printClassType, printList, printSingle } from "./helpers.js";
const { group, indent, join, line, softline } = builders;
export default {
primitiveType(path, print) {
const { children } = path.node;
const typeKey = onlyDefinedKey(children, ["Boolean", "numericType"]);
return join(" ", [
...map(path, print, "annotation"),
call(path, print, typeKey)
]);
},
numericType: printSingle,
integralType: printSingle,
floatingPointType: printSingle,
referenceType(path, print) {
const { children } = path.node;
const typeKey = onlyDefinedKey(children, [
"primitiveType",
"classOrInterfaceType"
]);
const type = call(path, print, typeKey);
return join(" ", [
...map(path, print, "annotation"),
children.dims ? [type, call(path, print, "dims")] : type
]);
},
classOrInterfaceType: printSingle,
classType: printClassType,
interfaceType: printSingle,
typeVariable(path, print) {
return join(" ", [
...map(path, print, "annotation"),
call(path, print, "Identifier")
]);
},
dims(path, print) {
return flatMap(path, childPath => {
const child = print(childPath);
return isNonTerminal(childPath.node) ? [child, " "] : child;
}, definedKeys(path.node.children, ["annotation", "LSquare", "RSquare"]));
},
typeParameter(path, print) {
const parameter = [
...map(path, print, "typeParameterModifier"),
call(path, print, "typeIdentifier")
];
if (path.node.children.typeBound) {
parameter.push(call(path, print, "typeBound"));
}
return join(" ", parameter);
},
typeParameterModifier: printSingle,
typeBound(path, print) {
const bound = ["extends ", call(path, print, "classOrInterfaceType")];
if (path.node.children.additionalBound) {
bound.push(group(indent([line, ...join(line, map(path, print, "additionalBound"))])));
}
return bound;
},
additionalBound(path, print) {
return ["& ", call(path, print, "interfaceType")];
},
typeArguments(path, print) {
return group([
"<",
indent([softline, call(path, print, "typeArgumentList")]),
softline,
">"
]);
},
typeArgumentList(path, print) {
return printList(path, print, "typeArgument");
},
typeArgument: printSingle,
wildcard(path, print) {
const wildcard = [...map(path, print, "annotation"), "?"];
if (path.node.children.wildcardBounds) {
wildcard.push(call(path, print, "wildcardBounds"));
}
return join(" ", wildcard);
},
wildcardBounds(path, print) {
return [
path.node.children.Extends ? "extends" : "super",
" ",
call(path, print, "referenceType")
];
}
};

View File

@@ -0,0 +1,4 @@
import type { Plugin } from "prettier";
declare const plugin: Plugin;
export default plugin;

View 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;

View 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;

File diff suppressed because it is too large Load Diff

View 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 };

View File

@@ -0,0 +1,4 @@
const loc = (prop) => (node) => node.loc?.[prop]?.offset;
export const locStart = loc("start");
export const locEnd = loc("end");

View 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;

View 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.",
},
};

View 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;

View 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 };

File diff suppressed because it is too large Load Diff

View 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,
};

View File

@@ -0,0 +1,824 @@
import { CommentOrDocComment, LocArray, Node, NodeType, NodeWithBodyOrCases } from "jinx-rust";
import {
end,
getBodyOrCases,
getLastParameter,
hasOuterAttributes,
isInner,
is_Attribute,
is_AttributeOrDocComment,
is_BlockCommentKind,
is_BlockCommentNode,
is_Comment,
is_CommentOrDocComment,
is_ExpressionWithBodyOrCases,
is_ExternSpecifier,
is_FlowControlExpression,
is_FunctionDeclaration,
is_FunctionNode,
is_IfBlockExpression,
is_LineCommentKind,
is_LineCommentNode,
is_LocArray,
is_MacroRule,
is_NodeWithBodyOrCases,
is_ReassignmentNode,
is_StatementNode,
is_StructLiteralProperty,
is_StructLiteralPropertySpread,
nisAnyOf,
ownStart,
start,
} from "jinx-rust/utils";
import { is_CallExpression_or_CallLikeMacroInvocation } from "../transform";
import { Narrow, assert, exit, iLast, last_of, maybe_last_of } from "../utils/common";
import { is_MemberAccessLike, is_xVariableEqualishLike } from "./core";
import {
AnyComment,
CustomOptions,
DCM,
Doc,
MutatedAttribute,
NodeWithComments,
PrettierCommentInfo,
breakParent,
cursor,
hardline,
indent,
join,
line,
lineSuffix,
literalline,
} from "./external";
import { assertPathAtNode, canAttachComment, getAllComments, getContext, getNode, getOptions, pathCallEach } from "./plugin";
import { shouldPrintOuterAttributesAbove } from "./styling";
function addCommentHelper(node: Node, comment: AnyComment, leading = false, trailing = false) {
__DEV__: assert(!handled(comment));
((node as NodeWithComments<Node>).comments ??= []).push(comment);
(comment.leading = leading), (comment.trailing = trailing), (comment.printed = false);
}
function addLeadingComment(node: Node, comment: AnyComment) {
addCommentHelper(node, comment, true);
}
function addDanglingComment(node: Node, comment: AnyComment, marker: DCM) {
addCommentHelper(node, comment);
comment.marker = marker;
}
function addTrailingComment(node: Node, comment: AnyComment) {
addCommentHelper(node, comment, false, true);
}
export function setPrettierIgnoreTarget(node: Node, comment: AnyComment) {
__DEV__: Narrow<Node & { prettierIgnore?: true }>(node), assert(isPrettierIgnoreComment(comment) || isPrettierIgnoreAttribute(comment));
comment.unignore = true;
node.prettierIgnore = true;
}
function hasComments<T extends Node>(node: T): node is NodeWithComments<T> {
return "comments" in node && node.comments.length > 0;
}
export function printDanglingComments(enclosingNode: Node, sameIndent: boolean, marker?: DCM) {
if (hasComments(enclosingNode)) {
const printed: Doc[] = [];
pathCallEach(enclosingNode, "comments", (comment) => {
if (isDangling(comment) && (!marker || comment.marker === marker)) {
printed.push(printComment(comment));
}
});
if (printed.length > 0) {
return sameIndent //
? join(hardline, printed)
: indent([hardline, join(hardline, printed)]);
}
}
return "";
}
export function needsHardlineAfterDanglingComment(node: Node) {
if (!hasComment(node)) return false;
const lastDanglingComment = maybe_last_of(getComments(node, CF.Dangling));
return lastDanglingComment && is_LineCommentNode(lastDanglingComment);
}
export function setDidPrintComment(comment: AnyComment) {
comment.printed = true;
}
function printComment(comment: AnyComment) {
__DEV__: assertPathAtNode("printComment", comment);
__DEV__: assert(handled(comment), `Assertion failed: Comment was not printed at ${comment.loc.url()}`, comment);
setDidPrintComment(comment);
return getContext().options.printer.printComment!(getContext().path as any, getOptions());
}
export function isPreviousLineEmpty(node: Node) {
let index = start(node) - 1;
index = skipSpaces(index, true) as number;
index = skipNewline(index, true) as number;
index = skipSpaces(index, true) as number;
return index !== skipNewline(index, true);
}
export function hasBreaklineBefore(node: Node) {
return hasNewline(start(node) - 1, true);
}
export function hasBreaklineAfter(node: Node) {
return hasNewline(end(node));
}
export function printCommentsSeparately(ignored?: Set<AnyComment>) {
const node = getNode();
__DEV__: Narrow<Node & { comments?: AnyComment[] }>(node);
const leading: Doc[] = [];
const trailing: Doc[] = [];
let hasTrailingLineComment = false;
let hadLeadingBlockComment = false;
if ("comments" in node) {
pathCallEach(node, "comments", (comment) => {
if (ignored?.has(comment)) {
return;
} else if (isLeading(comment)) {
leading.push(printLeadingComment(comment));
} else if (isTrailing(comment)) {
trailing.push(printTrailingComment(comment));
}
});
}
if (node === getOptions().cursorNode) {
leading.unshift(cursor);
trailing.push(cursor);
}
return (leading.length | trailing.length) > 0 ? { leading, trailing } : ({ leading: "", trailing: "" } as const);
function printLeadingComment(comment: AnyComment) {
if (is_Attribute(comment) && !comment.inner) {
const printed = printComment(comment);
return [printed, " "];
}
hadLeadingBlockComment ||= is_BlockCommentKind(comment) && hasBreaklineBefore(comment);
return [
printComment(comment),
is_BlockCommentKind(comment)
? hasBreaklineAfter(comment) //
? hadLeadingBlockComment
? hardline
: line
: " "
: hardline,
hasNewline(skipNewline(skipSpaces(end(comment)))) ? hardline : "",
];
}
function printTrailingComment(comment: AnyComment) {
const printed = printComment(comment);
return hasBreaklineBefore(comment)
? lineSuffix([hardline, isPreviousLineEmpty(comment) ? hardline : "", printed])
: is_BlockCommentNode(comment)
? [" ", printed]
: lineSuffix([" ", printed, hasTrailingLineComment === (hasTrailingLineComment = true) ? hardline : breakParent]);
}
}
export function getPostLeadingComment(comment: AnyComment) {
// console.log(comment.loc.url());
// is_BlockCommentKind(comment)
// ? hasBreaklineAfter(comment) //
// ? hasBreaklineBefore(comment)
// ? hardline
// : line
// : " "
// : hardline,
return hasNewline(skipNewline(skipSpaces(end(comment)))) ? hardline : "";
}
export function withComments<D extends Doc>(node: Node, printed: D, ignored?: Set<AnyComment>): D | Doc[] {
__DEV__: assertPathAtNode("withComments", node);
const { leading, trailing } = printCommentsSeparately(ignored);
return leading || trailing ? [...leading!, printed, ...trailing!] : printed;
// return needsOuterParens(node) ? group(["(", indent([softline, parts]), softline, ")"]) : parts;
// return parts;
}
export function getComments(node: Node, ...args: Parameters<typeof getCommentTestFunction>): AnyComment[] {
__DEV__: Narrow<Node & { comments?: AnyComment[] }>(node);
// if (!node || !node.comments) return [];
// if (args.length === 0) return node.comments;
// return args.length > 0 ? node.comments.filter(getCommentTestFunction(...args)) : node.comments;
return node && node.comments //
? args.length > 0
? node.comments.filter(getCommentTestFunction(...args))
: node.comments
: [];
}
export function getFirstComment(node: Node, flags: CF, fn?: (comment: AnyComment) => boolean): AnyComment | undefined {
const r = getComments(node, flags | CF.First, fn);
return r.length === 0 ? undefined : r[0];
}
export function escapeComments(flags: number, fn?: (comment: AnyComment) => boolean) {
const comments = getAllComments().filter(getCommentTestFunction(flags, fn)) as AnyComment[];
comments.forEach(setDidPrintComment);
return new Set(comments);
}
export const enum CF {
Leading = 1 << 1,
Trailing = 1 << 2,
Dangling = 1 << 3,
Block = 1 << 4,
Line = 1 << 5,
PrettierIgnore = 1 << 6,
First = 1 << 7,
Last = 1 << 8,
}
export function isPrettierIgnoreComment(comment: AnyComment) {
return is_Comment(comment) && /^\s*prettier-ignore\s*/.test(comment.value) && !comment.unignore;
}
export function isPrettierIgnoreAttribute(node: Node): node is MutatedAttribute {
return is_Attribute(node) && /^\s*rustfmt::skip\s*$/.test(node.value);
}
function getCommentTestFunction(flags: CF, fn?: (comment: AnyComment) => boolean) {
return function (comment: AnyComment, index: number, comments: AnyComment[]) {
__DEV__: Narrow<number>(flags), assert(handled(comment));
return !(
(flags & CF.Leading && !isLeading(comment)) ||
(flags & CF.Trailing && !isTrailing(comment)) ||
(flags & CF.Dangling && !isDangling(comment)) ||
(flags & CF.Block && !is_BlockCommentKind(comment)) ||
(flags & CF.Line && !is_LineCommentKind(comment)) ||
(flags & CF.First && index !== 0) ||
(flags & CF.Last && !iLast(index, comments)) ||
(flags & CF.PrettierIgnore && !(isPrettierIgnoreComment(comment) || isPrettierIgnoreAttribute(comment))) ||
(fn && !fn(comment))
);
};
}
export function hasComment(node: Node, flags: number = 0, fn?: (comment: AnyComment) => boolean) {
if ("comments" in node && node.comments!.length > 0) {
return flags || fn ? (node.comments as AnyComment[]).some(getCommentTestFunction(flags, fn)) : true;
}
return false;
}
export function hasNewlineInRange(leftIndex: number, rightIndex: number) {
__DEV__: assert(leftIndex <= rightIndex);
const text = getContext().options.originalText;
for (var i = leftIndex; i < rightIndex; ++i) if (text.charCodeAt(i) === 10) return true;
return false;
}
export function isNextLineEmpty(node: Node) {
return isNextLineEmptyAfterIndex(end(node));
}
export function isNextLineEmptyAfterIndex(index: number | false) {
let oldIdx: number | false = -1;
let idx: number | false = index;
while (idx !== oldIdx) {
oldIdx = idx;
idx = skipToLineEnd(idx);
idx = skipBlockComment(idx);
idx = skipSpaces(idx);
idx = skipParens(idx);
}
idx = skipLineComment(idx);
idx = skipParens(idx);
idx = skipNewline(idx);
idx = skipParens(idx);
return idx !== false && hasNewline(idx);
}
export function hasNewline(index: number | false, backwards = false) {
if (index === false) return false;
const i = skipSpaces(index, backwards);
return i !== false && i !== skipNewline(i, backwards);
}
function skipLineComment(index: number | false) {
if (index === false) return false;
const { commentSpans, originalText } = getContext().options;
if (commentSpans.has(index) && originalText.charCodeAt(index + 1) === 47 /** "/" */)
return skipEverythingButNewLine(commentSpans.get(index)!);
return index;
}
function skipBlockComment(index: number | false) {
if (index === false) return false;
const { commentSpans, originalText } = getContext().options;
if (commentSpans.has(index) && originalText.charCodeAt(index + 1) === 42 /** "*" */) return commentSpans.get(index)!;
return index;
}
const [skipSpaces, skipToLineEnd, skipEverythingButNewLine] = [/[ \t]/, /[,; \t]/, /[^\r\n]/].map(function (re) {
return function (index: number | false, backwards = false) {
if (index === false) return false;
const { originalText: text } = getContext().options;
let cursor = index;
while (cursor >= 0 && cursor < text.length) {
if (re.test(text.charAt(cursor))) backwards ? cursor-- : cursor++;
else return cursor;
}
return cursor === -1 || cursor === text.length ? cursor : false;
};
});
function skipNewline(index: number | false, backwards = false) {
if (index === false) return false;
const { originalText } = getContext().options;
const atIndex = originalText.charCodeAt(index);
if (backwards) {
if (originalText.charCodeAt(index - 1) === 13 && atIndex === 10) return index - 2;
if (atIndex === 10) return index - 1;
} else {
if (atIndex === 13 && originalText.charCodeAt(index + 1) === 10) return index + 2;
if (atIndex === 10) return index + 1;
}
return index;
}
function skipParens(index: number | false, backwards = false) {
return index;
// if (index === false) return false;
// const { parensPositions } = getContext().options;
// while (parensPositions.has(index)) backwards ? index-- : index++;
// return index;
}
export function getNextNonSpaceNonCommentCharacterIndex(node: Node) {
return getNextNonSpaceNonCommentCharacterIndexWithStartIndex(end(node));
}
function getNextNonSpaceNonCommentCharacterIndexWithStartIndex(i: number) {
let oldIdx = -1;
let nextIdx = i;
while (nextIdx !== oldIdx) {
oldIdx = nextIdx;
nextIdx = skipSpaces(nextIdx) as number;
nextIdx = skipBlockComment(nextIdx) as number;
nextIdx = skipLineComment(nextIdx) as number;
nextIdx = skipNewline(nextIdx) as number;
nextIdx = skipParens(nextIdx) as number;
}
return nextIdx;
}
export function getNextNonSpaceNonCommentCharacter(node: Node) {
return getContext().options.originalText.charAt(getNextNonSpaceNonCommentCharacterIndex(node));
}
interface CommentContext {
comment: AnyComment;
precedingNode: Node | undefined;
enclosingNode: Node | undefined;
followingNode: Node | undefined;
text: string;
options: CustomOptions;
ast: Node;
isLastComment: boolean;
}
function handled(comment: AnyComment) {
return "printed" in comment;
}
function handleCommon(ctx: CommentContext): boolean {
{
const { comment, precedingNode, enclosingNode, followingNode } = ctx;
if (!enclosingNode) {
ctx.enclosingNode = ctx.comment.loc.src.program;
} else if (enclosingNode && is_NodeWithBodyOrCases(enclosingNode)) {
const body = getBodyOrCases(enclosingNode);
if (body) {
if (is_ExpressionWithBodyOrCases(enclosingNode) && enclosingNode.label) {
if (ctx.precedingNode === enclosingNode.label) {
ctx.precedingNode = undefined;
}
if (followingNode === enclosingNode.label) {
ctx.followingNode = undefined;
}
}
if (comment.loc.isBefore(body)) {
if (followingNode && body.loc.contains(followingNode)) {
ctx.followingNode = undefined;
}
if (!ctx.precedingNode && !ctx.followingNode) {
addLeadingComment(enclosingNode, comment);
return true;
}
} else if (comment.loc.isAfter(body)) {
if (precedingNode && body.loc.contains(precedingNode)) {
ctx.precedingNode = undefined;
}
if (!ctx.precedingNode && !ctx.followingNode) {
addTrailingComment(enclosingNode, comment);
return true;
}
} else if (body.loc.contains(comment)) {
if (precedingNode && !body.loc.contains(precedingNode)) {
ctx.precedingNode = undefined;
}
if (followingNode && !body.loc.contains(followingNode)) {
ctx.followingNode = undefined;
}
}
}
}
}
for (const fn of [
handleMixedInOuterAttributeComments,
handleAttributeComments,
handleDanglingComments,
handleFunctionComments,
handleMacroRuleComments,
handleStructLiteralComments,
handleVariableDeclaratorComments,
handleIfBlockExpressionComments,
handleMemberExpressionComments,
handleStatementComments,
handleFlowControlComments,
handleBadComments,
]) {
fn(ctx);
if (handled(ctx.comment)) {
// console.log(ctx.comment.loc.url(), fn.name);
return true;
}
}
const { precedingNode, followingNode, comment } = ctx;
if (isStartOfLine(comment)) {
if (followingNode) {
addLeadingComment(followingNode, comment);
} else if (precedingNode) {
addTrailingComment(precedingNode, comment);
} else {
exit.never(ctx);
}
} else if (isEndOfLine(comment)) {
if (precedingNode) {
addTrailingComment(precedingNode, comment);
} else if (followingNode) {
addLeadingComment(followingNode, comment);
} else {
exit.never(ctx);
}
} else {
if (precedingNode && followingNode) {
return false;
} else if (precedingNode) {
addTrailingComment(precedingNode, comment);
} else if (followingNode) {
addLeadingComment(followingNode, comment);
} else {
exit.never(ctx);
}
}
return handled(ctx.comment);
}
export function handleOwnLineComment(ctx: CommentContext) {
return handleCommon(ctx);
}
export function handleEndOfLineComment(ctx: CommentContext) {
const { precedingNode, enclosingNode, comment } = ctx;
if (
// handleCallExpressionComments
precedingNode &&
enclosingNode &&
is_CallExpression_or_CallLikeMacroInvocation(enclosingNode) &&
enclosingNode.arguments.length > 0 &&
precedingNode === (enclosingNode.typeArguments ? last_of(enclosingNode.typeArguments) : enclosingNode.callee)
) {
addLeadingComment(enclosingNode.arguments[0], comment);
return true;
} else if (
// handlePropertyComments
enclosingNode &&
is_StructLiteralProperty(enclosingNode)
) {
addLeadingComment(enclosingNode, comment);
return true;
} else {
return handleCommon(ctx);
}
}
export function handleRemainingComment(ctx: CommentContext) {
return handleCommon(ctx);
}
function handleStructLiteralComments({ enclosingNode, followingNode, comment }: CommentContext) {
if (enclosingNode && is_StructLiteralPropertySpread(enclosingNode) && followingNode === enclosingNode.expression) {
addLeadingComment(enclosingNode, comment);
}
}
function handleVariableDeclaratorComments({ enclosingNode, followingNode, comment }: CommentContext) {
if (
enclosingNode &&
(is_xVariableEqualishLike(enclosingNode) || is_ReassignmentNode(enclosingNode)) &&
followingNode &&
(is_BlockCommentKind(comment) ||
nisAnyOf(followingNode, [
NodeType.StructLiteral,
NodeType.StructPattern,
NodeType.TupleLiteral,
NodeType.TypeTuple,
NodeType.TuplePattern,
NodeType.ArrayLiteral,
NodeType.ArrayPattern,
NodeType.SizedArrayLiteral,
NodeType.TypeSizedArray,
]))
) {
addLeadingComment(followingNode, comment);
}
}
function handleMixedInOuterAttributeComments({ precedingNode, enclosingNode, followingNode, comment }: CommentContext) {
if (enclosingNode && hasOuterAttributes(enclosingNode) && end(comment) <= ownStart(enclosingNode)) {
if (isPrettierIgnoreComment(comment) || isPrettierIgnoreAttribute(comment)) {
setPrettierIgnoreTarget(enclosingNode, comment);
}
if (isEndOfLine(comment)) {
__DEV__: assert(!!precedingNode && is_Attribute(precedingNode), "", precedingNode);
if (shouldPrintOuterAttributesAbove(enclosingNode)) {
// #[attr] // comment
// node
addTrailingComment(precedingNode, comment);
} else {
// #[attr] /* comment */ node
addLeadingComment(followingNode || enclosingNode, comment);
}
} else {
// __DEV__: assert(isStartOfLine(comment));
if (followingNode && end(followingNode) <= ownStart(enclosingNode)) {
addLeadingComment(followingNode, comment);
} else if (precedingNode && enclosingNode.loc.contains(precedingNode)) {
addTrailingComment(precedingNode, comment);
} else {
addLeadingComment(enclosingNode, comment);
}
}
}
}
function handleAttributeComments({ precedingNode, enclosingNode, followingNode, comment, ast }: CommentContext) {
if (is_AttributeOrDocComment(comment)) {
if (
comment.inner &&
enclosingNode &&
is_FunctionDeclaration(enclosingNode) &&
(!followingNode || !is_StatementNode(followingNode)) &&
(!precedingNode || !is_StatementNode(precedingNode))
) {
if (enclosingNode.body) {
if (canAttachCommentInLocArray(enclosingNode.body)) {
addDanglingComment(enclosingNode, comment, DCM["body"]);
} else {
addLeadingComment(enclosingNode.body[0], comment);
}
} else {
addLeadingComment(enclosingNode, comment);
}
} else {
// if (comment.loc.url().startsWith("tests/samples/macro/attr.rs") && getContext().options.danglingAttributes.includes(comment)) {
// // debugger;
// console.log({
// comment: comment.loc.url(),
// precedingNode: precedingNode?.loc.url(),
// enclosingNode: enclosingNode?.loc.url(),
// followingNode: followingNode?.loc.url(),
// });
// }
if (followingNode) {
addLeadingComment(followingNode, comment);
} else if (enclosingNode) {
for (var key in DCM)
if (key in enclosingNode) {
addDanglingComment(enclosingNode, comment, key as DCM);
return;
}
} else {
addDanglingComment(ast, comment, DCM["body"]);
}
}
}
}
function handleBadComments({ precedingNode, enclosingNode, followingNode, ast, comment }: CommentContext) {
if (!enclosingNode) {
// console.log(comment.loc.url());
if (followingNode) {
addLeadingComment(followingNode, comment);
} else if (precedingNode) {
addTrailingComment(precedingNode, comment);
} else {
addDanglingComment(enclosingNode || ast, comment, DCM["body"]);
}
} else if (!precedingNode && !followingNode) {
if (enclosingNode && enclosingNode !== ast) {
addLeadingComment(enclosingNode, comment);
} else {
addDanglingComment(ast, comment, DCM["body"]);
}
}
}
function is_ABI_Comment({ precedingNode, enclosingNode, comment }: CommentContext) {
return (
is_CommentOrDocComment(comment) &&
((precedingNode && is_ExternSpecifier(precedingNode)) || (enclosingNode && is_ExternSpecifier(enclosingNode)))
);
}
function handleFlowControlComments({ precedingNode, enclosingNode, followingNode, comment }: CommentContext) {
if (enclosingNode && is_FlowControlExpression(enclosingNode)) {
if (!precedingNode && (isOwnLine(comment) || isEndOfLine(comment)) && !followingNode) {
addLeadingComment(enclosingNode, comment);
}
}
}
function handleFunctionComments(ctx: CommentContext) {
const { precedingNode, enclosingNode, followingNode, comment } = ctx;
if (enclosingNode && is_FunctionNode(enclosingNode)) {
if (
is_FunctionDeclaration(enclosingNode) &&
((!is_ABI_Comment(ctx) && comment.loc.isBefore(enclosingNode.generics || enclosingNode.id)) ||
(enclosingNode.generics && comment.loc.isBetween(enclosingNode.generics, enclosingNode.parameters)))
) {
addLeadingComment(enclosingNode, comment);
} else if (
!enclosingNode.returnType &&
comment.loc.isBetween(
enclosingNode.parameters,
is_FunctionDeclaration(enclosingNode) ? enclosingNode.body! : enclosingNode.expression
)
) {
if (is_FunctionDeclaration(enclosingNode)) {
addCommentToBlock(enclosingNode, comment);
} else {
addLeadingComment(enclosingNode.expression, comment);
}
} else if (
precedingNode && //
enclosingNode.parameters.loc.contains(comment)
) {
if (precedingNode === getLastParameter(enclosingNode)) {
addTrailingComment(precedingNode, comment);
}
} else if (
followingNode &&
isStartOfLine(comment) &&
comment.loc.isAfter(enclosingNode.parameters) &&
(!is_FunctionDeclaration(enclosingNode) || !enclosingNode.whereBounds || comment.loc.isAfter(enclosingNode.whereBounds!)) &&
(!enclosingNode.returnType || comment.loc.isAfter(enclosingNode.returnType)) &&
followingNode === (is_FunctionDeclaration(enclosingNode) ? enclosingNode.body?.[0] : enclosingNode.expression)
) {
addLeadingComment(followingNode, comment);
}
}
}
function handleMacroRuleComments(ctx: CommentContext) {
const { precedingNode, enclosingNode, followingNode, comment } = ctx;
if (enclosingNode && is_MacroRule(enclosingNode)) {
if (enclosingNode.transform.loc.contains(comment)) {
__DEV__: assert(enclosingNode.transform.length > 0);
if (!precedingNode || !enclosingNode.transform.loc.contains(precedingNode)) {
__DEV__: assert(!!followingNode && enclosingNode.transform.loc.contains(followingNode));
addLeadingComment(followingNode, comment);
}
} else if (enclosingNode.match.loc.contains(comment)) {
__DEV__: assert(enclosingNode.match.length > 0);
if (!followingNode || !enclosingNode.match.loc.contains(followingNode)) {
__DEV__: assert(!!precedingNode && enclosingNode.match.loc.contains(precedingNode));
addTrailingComment(precedingNode!, comment);
}
}
}
}
function handleStatementComments(ctx: CommentContext) {
const { precedingNode, comment } = ctx;
if (isEndOfLine(comment) && precedingNode && (is_StatementNode(precedingNode) || precedingNode.loc.sliceText().endsWith(";"))) {
addTrailingComment(precedingNode, comment);
}
}
function addCommentToBlock(block: NodeWithBodyOrCases, comment: AnyComment) {
const body = getBodyOrCases(block);
__DEV__: assert(!!body);
if (body.length > 0) {
addLeadingComment(body![0], comment);
} else {
addDanglingComment(block, comment, DCM["body"]);
}
}
function handleIfBlockExpressionComments(ctx: CommentContext) {
const { comment, enclosingNode } = ctx;
if (enclosingNode && is_IfBlockExpression(enclosingNode)) {
const { condition, body, else: else_ } = enclosingNode;
if (comment.loc.isBefore(condition)) {
addLeadingComment(condition, comment);
} else if (comment.loc.isBetween(condition, body)) {
addTrailingComment(condition, comment);
} else if (else_ && comment.loc.isBetween(body, else_)) {
if (is_IfBlockExpression(else_)) {
addLeadingComment(else_.condition, comment);
} else {
addCommentToBlock(else_, comment);
}
}
}
}
function handleMemberExpressionComments({ comment, precedingNode, enclosingNode }: CommentContext) {
if (enclosingNode && is_MemberAccessLike(enclosingNode)) {
if (isStartOfLine(comment) || !precedingNode) addLeadingComment(enclosingNode, comment);
else addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
function handleDanglingComments({ comment, enclosingNode }: CommentContext) {
if (enclosingNode) {
for (var key in DCM) {
if (key in enclosingNode) {
var arr: LocArray = enclosingNode[key];
if (is_LocArray(arr) && canAttachCommentInLocArray(arr) && arr.loc.contains(comment)) {
addDanglingComment(enclosingNode, comment, key as DCM);
return;
}
}
}
}
}
function canAttachCommentInLocArray(arr: LocArray) {
return arr.length === 0 || arr.every((node) => !canAttachComment(node));
}
function isOwnLine(comment: AnyComment) {
return isStartOfLine(comment) && hasBreaklineAfter(comment);
}
function isStartOfLine(comment: AnyComment) {
return comment.placement === "ownLine";
}
function isEndOfLine(comment: AnyComment) {
return comment.placement === "endOfLine";
}
export function isDangling(comment: AnyComment) {
__DEV__: assert(handled(comment));
return !comment.leading && !comment.trailing;
}
export function isLeading(comment: AnyComment) {
__DEV__: assert(handled(comment));
return comment.leading && !comment.trailing;
}
export function isTrailing(comment: AnyComment) {
__DEV__: assert(handled(comment));
return !comment.leading && comment.trailing;
}
export function print_comment(comment: CommentOrDocComment) {
__DEV__: Narrow<PrettierCommentInfo>(comment);
const doc = is_BlockCommentNode(comment)
? isIndentableBlockComment(comment.value)
? [
(!handled(comment) || isTrailing(comment)) && !hasBreaklineBefore(comment) ? hardline : "",
getCommentStart(comment),
...comment.value.split(/\n/g).map((line, i, a) =>
i === 0 //
? [line.trimEnd(), hardline]
: !iLast(i, a)
? [" " + line.trim(), hardline]
: " " + line.trimStart()
),
"*/",
]
: [
getCommentStart(comment), //
join(literalline, comment.value.split(/\n/g)),
"*/",
]
: [getCommentStart(comment), comment.value.trimEnd()];
return handled(comment) && isDangling(comment) //
? [doc, getPostLeadingComment(comment)]
: doc;
function getCommentStart(comment: CommentOrDocComment) {
return is_Comment(comment)
? is_BlockCommentKind(comment)
? "/*"
: "//"
: is_BlockCommentKind(comment)
? isInner(comment)
? "/*!"
: "/**"
: isInner(comment)
? "//!"
: "///";
}
function isIndentableBlockComment(value: string) {
const lines = `*${value}*`.split(/\n/g);
return lines.length > 1 && lines.every((line) => /^\s*\*/.test(line));
}
}

View File

@@ -0,0 +1,282 @@
import {
ForLtParametersBody,
FunctionSpread,
GenericParameterDeclaration,
MaybeGenericArgsTarget,
MissingNode,
Node,
NodeType,
TypeBound,
TypeBoundsConstaint,
TypeCallArgument,
TypeNamespaceTargetNoSelector,
TypeNode,
} from "jinx-rust";
import {
getAstPath,
getOwnChildAstPath,
is_BareTypeTraitBound,
is_FunctionSpread,
is_LetScrutinee,
is_Literal,
is_MissingNode,
is_TypeBoundsStandaloneNode,
is_TypeFunctionNode,
is_TypeNode,
is_VariableDeclarationNode,
} from "jinx-rust/utils";
import { exit, has_key_defined, last_of, spliceAll } from "../utils/common";
import { canBreak } from "./external";
import { getContext, getNode, getOptions, getPrintFn } from "./plugin";
let DEPTH = 0;
const ANCESTRY: Node[] = [];
const LONE_SHORT_ARGUMENT_THRESHOLD_RATE = 0.25;
export function withCheckContext<R>(fn: () => R): R {
if (0 === DEPTH) {
return fn();
} else {
DEPTH = 0;
const prev = spliceAll(ANCESTRY);
try {
return fn();
} finally {
DEPTH = ANCESTRY.push(...prev);
}
}
}
export function is_short(str: string) {
return str.length <= LONE_SHORT_ARGUMENT_THRESHOLD_RATE * getOptions().printWidth;
}
function print(target: Node) {
const current = getNode();
const keys: (string | number)[] = [...getAstPath(ANCESTRY[0], getNode())];
for (let i = 1; i < ANCESTRY.length; i++) keys.push(...getOwnChildAstPath(ANCESTRY[i - 1], ANCESTRY[i]));
keys.push(...getOwnChildAstPath(last_of(ANCESTRY), target));
try {
return getContext().path.call(() => getPrintFn(target)(), ...keys);
} catch (e) {
console.log({ current, target, keys, ANCESTRY });
throw e;
}
}
function IsSimpleFunction<T extends Node>(fn: (node: T) => boolean): (node: T) => boolean {
return function (node: T) {
if (0 !== DEPTH && node === ANCESTRY[DEPTH - 1]) {
return fn(node);
}
if (DEPTH >= 2) {
return isShortBasic(node);
}
try {
return fn((ANCESTRY[DEPTH++] = node) as any);
} finally {
ANCESTRY.length = --DEPTH;
}
} as any;
}
function HasComplexFunction<T extends Node>(fn: (node: T) => boolean): (node: T) => boolean {
return function (node: T) {
if (0 !== DEPTH && node === ANCESTRY[DEPTH - 1]) {
return fn(node);
}
if (DEPTH >= 2) {
return !isShortBasic(node);
}
try {
return fn((ANCESTRY[DEPTH++] = node) as any);
} finally {
ANCESTRY.length = --DEPTH;
}
} as any;
}
const isShortBasic = (node: Node) => {
switch (node.nodeType) {
case NodeType.MissingNode:
return true;
case NodeType.Identifier:
case NodeType.Index:
case NodeType.LtIdentifier:
case NodeType.LbIdentifier:
case NodeType.McIdentifier:
return is_short(node.name);
case NodeType.Literal:
return is_short(node.value) && !/\n/.test(node.value);
}
return false;
};
export const isSimpleType = IsSimpleFunction<FunctionSpread | TypeNode | MissingNode>((node): boolean => {
switch (node.nodeType) {
case NodeType.MissingNode:
case NodeType.FunctionSpread:
return true;
case NodeType.MacroInvocation:
return false;
case NodeType.Identifier:
case NodeType.TypeNever:
case NodeType.TypeInferred:
return true;
case NodeType.TypePath:
return isShortBasic(node.segment) && (!node.namespace || isSimpleType(node.namespace));
case NodeType.TypeCall:
return isSimpleType(node.typeCallee) && !hasComplexTypeArguments(node);
case NodeType.ExpressionTypeSelector:
return isSimpleType(node.typeTarget) && (!node.typeExpression || isSimpleType(node.typeExpression));
case NodeType.TypeDynBounds:
return !hasComplexTypeBounds(node);
case NodeType.TypeImplBounds:
return !hasComplexTypeBounds(node);
case NodeType.TypeFnPointer: {
const param = node.parameters[0];
return (
(!node.extern || !node.extern.abi || isShortBasic(node.extern.abi)) &&
!hasComplexLtParameters(node) &&
(node.parameters.length === 0 ||
(node.parameters.length === 1 &&
(is_FunctionSpread(param) ||
(!is_TypeFunctionNode(param.typeAnnotation) && isSimpleType(param.typeAnnotation))))) &&
(!node.returnType || isSimpleType(node.returnType))
);
}
case NodeType.TypeFunction:
return isSimpleType(node.callee) && node.parameters.every(isSimpleType) && (!node.returnType || isSimpleType(node.returnType));
case NodeType.TypeSizedArray:
return isSimpleType(node.typeExpression) && isShortBasic(node.sizeExpression);
case NodeType.TypeSlice:
return isSimpleType(node.typeExpression);
case NodeType.TypeTuple:
return node.items.length === 0 || (node.items.length === 1 && isSimpleType(node.items[0]));
case NodeType.TypeReference:
case NodeType.TypeDereferenceMut:
case NodeType.TypeDereferenceConst:
case NodeType.TypeParenthesized:
return isSimpleType(node.typeExpression);
default:
__DEV__: exit.never(node);
return false;
}
});
export const hasComplexTypeBounds = HasComplexFunction<Extract<Node, TypeBoundsConstaint>>((node) => {
return !!node.typeBounds && node.typeBounds.length > 1 && !node.typeBounds.every(isSimpleTypeBound);
});
export const isSimpleTypeBound = (node: TypeBound): boolean => {
switch (node.nodeType) {
case NodeType.TypeParenthesized:
return isSimpleTypeBound(node.typeExpression);
// #Lifetime
case NodeType.LtIdentifier:
case NodeType.LtElided:
case NodeType.LtStatic:
return true;
case NodeType.TypeTraitBound:
return is_BareTypeTraitBound(node) && isSimpleTypeNamespaceTargetNoSelector(node.typeExpression);
default:
__DEV__: exit.never(node);
return false;
}
function isSimpleTypeNamespaceTargetNoSelector(node: TypeNamespaceTargetNoSelector): boolean {
switch (node.nodeType) {
case NodeType.Identifier:
return true;
case NodeType.TypePath:
return undefined === node.namespace || isSimpleTypeNamespaceTargetNoSelector(node.namespace);
case NodeType.TypeCall:
return false;
case NodeType.TypeFunction:
return isSimpleTypeNamespaceTargetNoSelector(node.callee) && node.parameters.length === 0 && !node.returnType;
default:
__DEV__: exit.never(node);
return false;
}
}
};
const isSimpleTypeArgument = IsSimpleFunction<TypeCallArgument>((node) => {
if (is_TypeNode(node)) {
return isSimpleType(node);
}
switch (node.nodeType) {
// #Lifetime
case NodeType.LtIdentifier:
case NodeType.LtElided:
case NodeType.LtStatic:
case NodeType.Literal:
return true;
case NodeType.MinusExpression:
return is_Literal(node.expression);
case NodeType.BlockExpression:
return false; //willBreak(getPrintFn(node)("body"));
case NodeType.TypeCallNamedArgument:
return isSimpleType(node.typeExpression);
case NodeType.TypeCallNamedBound:
return isSimpleType(node.typeTarget) && !hasComplexTypeBounds(node);
default:
__DEV__: exit.never(node);
return false;
}
});
export const hasComplexTypeArguments = HasComplexFunction<Extract<Node, MaybeGenericArgsTarget>>((node) =>
!node.typeArguments || node.typeArguments.length === 0
? false
: node.typeArguments.length === 1
? (() => {
const arg = node.typeArguments[0];
return is_TypeBoundsStandaloneNode(arg) || canBreak(print(arg));
})()
: true
);
export const hasComplexLtParameters = HasComplexFunction<Extract<Node, ForLtParametersBody>>((node) => {
const ltParameters = node.ltParameters;
if (!ltParameters || ltParameters.length === 0) {
return false;
}
if (ltParameters.length === 1) {
const arg = ltParameters[0];
if (arg.ltBounds && arg.ltBounds.length > 1) {
return true;
}
return false;
}
return true;
});
export const isShortGenericParameterDeclaration = IsSimpleFunction<GenericParameterDeclaration>((node) => {
switch (node.nodeType) {
case NodeType.GenericTypeParameterDeclaration:
return !node.typeBounds && !node.typeDefault;
case NodeType.ConstTypeParameterDeclaration:
return (!node.typeAnnotation || is_MissingNode(node)) && !node.typeDefault;
case NodeType.GenericLtParameterDeclaration:
return !node.ltBounds;
default:
exit.never();
}
});
export const hasComplexGenerics = HasComplexFunction<Node>((node) => {
return has_key_defined(node, "generics") && node.generics.length > 0 && !node.generics.every(isShortGenericParameterDeclaration);
});
export const hasComplexTypeAnnotation = HasComplexFunction<Node>((node) => {
if (is_VariableDeclarationNode(node) && !is_LetScrutinee(node)) {
const { typeAnnotation } = node;
return !!typeAnnotation && !is_MissingNode(typeAnnotation) && !isSimpleType(typeAnnotation);
} else {
return false;
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,126 @@
import { Attribute, AttributeOrDocComment, Comment, DocCommentAttribute, LocArray, MemberExpression, Node, SourceFile } from "jinx-rust";
import { PickProps } from "jinx-rust/utils";
import type { Doc, ParserOptions, Printer } from "prettier";
import { doc } from "prettier";
import { AssertTypesEq } from "../utils/common";
export type { Doc, ParserOptions, Plugin, Printer } from "prettier";
export const {
join,
line,
softline,
hardline,
literalline,
group,
conditionalGroup,
fill,
lineSuffix,
lineSuffixBoundary,
cursor,
breakParent,
ifBreak,
trim,
indent,
indentIfBreak,
align,
addAlignmentToDoc,
markAsRoot,
dedentToRoot,
dedent,
hardlineWithoutBreakParent,
literallineWithoutBreakParent,
label,
} = doc.builders;
export const {
willBreak,
traverseDoc,
findInDoc,
mapDoc,
removeLines,
stripTrailingHardline,
} = doc.utils;
// Fallback implementations for removed common in prettier 3
export const isConcat = (doc: any): boolean => Array.isArray(doc);
export const getDocParts = (doc: any): any[] => Array.isArray(doc) ? doc : [doc];
export const propagateBreaks = (doc: any): any => doc;
export const normalizeParts = (parts: any[]): any[] => parts.flat();
export const normalizeDoc = (doc: any): any => doc;
export const cleanDoc = (doc: any): any => doc;
export const canBreak = (doc: any): boolean => {
if (!doc) return false;
if (typeof doc === 'string') return false;
if (Array.isArray(doc)) return doc.some(canBreak);
if (doc.type === 'group' || doc.type === 'fill') return true;
return willBreak(doc);
};
export const Symbol_comments = Symbol.for("comments");
export interface CustomOptions extends ParserOptions<Node> {
[Symbol_comments]: AnyComment[];
rsParsedFile: SourceFile;
commentSpans: Map<number, number>;
printer: Printer<Node>;
cursorNode: any;
comments: Comment[];
danglingAttributes: AttributeOrDocComment[];
actuallyMethodNodes: WeakSet<MemberExpression>;
}
export type NodeWithComments<T extends Node> = T & { comments: AnyComment[] };
export interface MutatedComment extends Comment, PrettierCommentInfo {}
export interface MutatedAttribute extends Attribute, PrettierCommentInfo {}
export interface MutatedDocComment extends DocCommentAttribute, PrettierCommentInfo {}
export type AnyComment = MutatedComment | MutatedAttribute | MutatedDocComment;
type keyofDelimitedArrayProps<T> = T extends never ? never : keyof PickProps<T, LocArray<any, "()" | "[]" | "{}" | "<>">>;
__DEV__: AssertTypesEq<keyof typeof DCM, keyofDelimitedArrayProps<Node>>();
export enum DCM {
"arguments" = "arguments",
"parameters" = "parameters",
"items" = "items",
"properties" = "properties",
"members" = "members",
"body" = "body",
"cases" = "cases",
"typeArguments" = "typeArguments",
"ltParameters" = "ltParameters",
"generics" = "generics",
"specifiers" = "specifiers",
"rules" = "rules",
"match" = "match",
"transform" = "transform",
"segments" = "segments",
}
export interface PrettierCommentInfo {
trailing: boolean;
leading: boolean;
unignore: boolean;
printed: boolean;
placement: "ownLine" | "endOfLine" | "remaining";
// nodeDescription?: any;
marker?: DCM;
}
export interface AstPath<T = Node> {
stack: (Node | string | number)[];
callParent<R>(callback: (path: this) => R, count?: number): R;
getName(): PropertyKey | null;
getValue(): T;
getNode(count?: number): T | null;
getParentNode(count?: number): T | null;
match(...predicates: ((node: Node, name: string | null, number: number | null) => boolean)[]): boolean;
call<R>(callback: (path: AstPath, index: number, value: any) => R, ...props: (string | number)[]): R;
each(callback: (path: AstPath, index: number, value: any) => void, ...props: (string | number)[]): void;
map<R>(callback: (path: AstPath, index: number, value: any) => R, ...props: (string | number)[]): R[];
}

View File

@@ -0,0 +1,367 @@
import { AttributeOrComment, IfBlockExpression, Node, Program, rs } from "jinx-rust";
import {
ArrayProps,
BoolProps,
NodeProps,
end,
hasAttributes,
insertNodes,
is_Attribute,
is_AttributeOrComment,
is_BlockCommentKind,
is_BlockCommentNode,
is_Comment,
is_DocCommentAttribute,
is_ElseBlock,
is_LineCommentNode,
is_MacroInvocation,
is_MacroRule,
is_MissingNode,
is_Node,
is_PunctuationToken,
is_UnionPattern,
start,
} from "jinx-rust/utils";
import { getCommentChildNodes, isTransformed, transform_ast } from "../transform";
import { Narrow, assert, color, each, exit, iLast, is_array, map_tagged_template, print_string } from "../utils/common";
import {
CF,
escapeComments,
getComments,
handleEndOfLineComment,
handleOwnLineComment,
handleRemainingComment,
hasBreaklineAfter,
hasComment,
isDangling,
isPrettierIgnoreAttribute,
setDidPrintComment,
withComments,
} from "./comments";
import { withCheckContext } from "./complexity";
import { isNoopExpressionStatement, maybeEmptyLine } from "./core";
import { AstPath, CustomOptions, Doc, Plugin, Symbol_comments, group, hardline, indent, line, softline, ParserOptions } from "./external";
import { printer } from "./printer";
import { needsInnerParens, needsOuterSoftbreakParens, shouldPrintOuterAttributesAbove } from "./styling";
export function is_printing_macro() {
return getContext().path.stack.some((node) => is_Node(node) && (is_MacroInvocation(node) || is_Attribute(node)));
}
export function assertPathAtNode(name: string, node: Node, ...ctx: any[]) {
__DEV__: if (getNode() !== node)
exit(`Attempted to call ${name}() in wrong prettier path context`, { asserted: node, actual: getNode() }, ...ctx);
}
export function f(...args: [strings: TemplateStringsArray, ...values: Doc[]]) {
let cancel = false;
const res = map_tagged_template(args, (doc) => {
cancel ||= !doc || (is_array(doc) && doc.length === 0);
return doc;
});
return cancel ? "" : res;
}
export function sg_single(s: TemplateStringsArray, v_0: Doc) {
return group([s[0], indent([softline, v_0]), softline, s[1]]);
}
export function sg_duo(s: TemplateStringsArray, v_0: Doc, v_1: Doc) {
return group([s[0], indent([softline, v_0, s[1], line, v_1]), softline, s[2]]);
}
let ctx: {
path: AstPath;
options: CustomOptions;
print: (path?: AstPath | string | [] | undefined, args?: any) => Doc;
args: any;
};
export const getNode = () => ctx.path.stack[ctx.path.stack.length - 1] as Node;
export const stackIncludes = (x: Node | string | number) => ctx.path.stack.includes(x);
export const getContext = () => ctx;
export const getOptions = () => ctx.options;
export const getProgram = () => ctx.options.rsParsedFile.program;
export const getAllComments = () => ctx.options[Symbol_comments];
export const getParentNode = (child?: Node) => {
__DEV__: if (child) assertPathAtNode("getParentNode", child);
return ctx.path.getParentNode();
};
export const getGrandParentNode = () => ctx.path.getParentNode(1) as Node;
export const getPrintFn = <T extends Node>(forNode?: T | undefined): print<T> => {
__DEV__: if (forNode) assertPathAtNode("getPrintFn", forNode);
return print as print<T>;
};
const get = (property: keyof any) => getNode()[property];
const has = (property: keyof any) => !!get(property);
export function pathCall<T extends Node, K extends keyof NodeProps<T> & keyof T, R>(node: T, key: K, fn: (child: T[K]) => R): R {
return ctx.path.call(() => fn(getNode() as any), key as any);
}
export function pathCallEach<T extends Node, K extends AK<T>>(
node: T,
key: K, // @ts-expect-error
fn: (child: NonNullable<T[K]>[number], index: number) => void
) {
__DEV__: assertPathAtNode("", node); // @ts-expect-error
ctx.path.each((_, i) => fn(getNode() as any, i), key);
}
export function pathCallAtParent<T extends Node, R>(parent: T, fn: (parent: T) => R): R {
return ctx.path.callParent(() => {
__DEV__: assertPathAtNode("pathCallParent", parent);
return fn(parent);
});
}
export function pathCallParentOf<T extends Node, R>(child: Node, fn: (parent: T) => R): R {
__DEV__: assertPathAtNode("pathCallParentOf", child);
return ctx.path.callParent((p) => fn(getNode() as any));
}
export function pathCallTopMostIfBlockExpression<R>(node: IfBlockExpression, fn: (node: IfBlockExpression) => R): R {
const parent = getParentNode(node)!;
return is_ElseBlock(node, parent) ? pathCallAtParent(parent, (parent) => pathCallTopMostIfBlockExpression(parent, fn)) : fn(node);
}
function print(property?: any, args?: any): Doc | Doc[] {
if (!property) return ctx.print(undefined!, args);
if (Array.isArray(property)) return ctx.print(property as any, args);
const value = get(property);
return !!value ? (Array.isArray(value) ? ctx.path.map(ctx.print, property) : ctx.print(property, args)) : "";
}
namespace print {
export function b(property: string, res = `${property} `): Doc {
return has(property) ? res : "";
}
export function map(property: string, mapItem?: MapFn<any, any>): Doc[] {
return !has(property) ? [] : ctx.path.map(mapItem ? (p, i, a) => mapItem(a[i], i, a) : () => ctx.print(), property);
}
export function join(property: string, sep: SepFn<any, any> | Doc, trailingSep: TrailingSepFn<any, any> | Doc = ""): Doc[] {
return map_join(property, () => ctx.print(), sep, trailingSep);
}
export function map_join(
property: string,
mapFn: MapFn<any, any>,
sep: SepFn<any, any> | Doc,
sepTrailing: TrailingSepFn<any, any> | Doc = ""
) {
const sepFn = typeof sep === "function" ? sep : () => sep;
return map(property, (v, i, a) => [
mapFn(v, i, a),
iLast(i, a as any)
? typeof sepTrailing === "function"
? sepTrailing(v)
: sepTrailing
: sepFn(v, a[i + 1], i === 0 ? undefined : a[i - 1]),
]);
}
}
// prettier-ignore
type SepFn<T extends Node = Node, K extends AK<T> = AK<T>> = <A extends AV<T, K>>(item: A[number], next_item: A[number], prev_item: A[number] | undefined) => Doc;
type MapFn<T extends Node = Node, K extends AK<T> = AK<T>> = <A extends AV<T, K>>(item: A[number], index: number, arr: A) => Doc;
type TrailingSepFn<T extends Node = Node, K extends AK<T> = AK<T>> = <A extends AV<T, K>>(item: A[number]) => Doc;
type AV<T extends Node, K extends keyof T> = Extract<NonNullable<T[K]>, ReadonlyArray<unknown>>;
type AK<T extends Node> = keyof ArrayProps<T> & keyof T;
// type AK<T extends Node> = keyof PickProps<T, {nodeType:number}|{nodeType:number}[]> & keyof T;
export interface print<T extends Node> {
(property?: [], args?: any): Doc;
(property?: [AK<T>, number], args?: any): Doc;
(property?: AK<T>, args?: any): Doc[];
// (property?: T extends {rules:{nodeType:number}|{nodeType:number}[]} ? "rules" : never, args?: any): Doc[];
(property?: keyof NodeProps<T> & keyof T, args?: any): Doc;
b(property: keyof BoolProps<T>, res?: string): Doc;
map<K extends AK<T>>(property: K & keyof ArrayProps<T>, mapFn?: MapFn<T, K>): Doc[];
join<K extends AK<T>>(property: K, sep: SepFn<T, K> | Doc, trailingSep?: TrailingSepFn<T, K> | Doc): Doc[];
map_join<K extends AK<T>>(property: K, mapFn: MapFn<T, K>, sep: SepFn<T, K> | Doc, trailingSep?: TrailingSepFn<T, K> | Doc): Doc[];
}
function genericPrint() {
return withCheckContext(() => {
const node = getNode();
__DEV__: assert(node.nodeType in printer);
let printed: Doc = hasPrettierIgnore(node) //
? node.loc.getOwnText()
: printer[node.nodeType]!(print as any, node as never);
const inner_parens = needsInnerParens(node);
if (inner_parens) {
printed = group(["(", printed, ")"]);
}
if (hasAttributes(node)) {
const print_above = shouldPrintOuterAttributesAbove(node); /* || node.attributes.length > 1 */
printed = [
...print.join(
"attributes",
(attr) =>
print_above
? maybeEmptyLine(attr)
: is_LineCommentNode(attr) || (is_BlockCommentNode(attr) && hasBreaklineAfter(attr))
? hardline
: " ",
(attr) =>
print_above && is_DocCommentAttribute(attr)
? maybeEmptyLine(attr)
: print_above || is_LineCommentNode(attr) || (is_BlockCommentNode(attr) && hasBreaklineAfter(attr))
? hardline
: " "
),
printed,
];
}
printed = withComments(
node,
printed,
hasPrettierIgnore(node) || ((is_Attribute(node) || is_MacroInvocation(node)) && !isTransformed(node))
? escapeComments(0, (comment) => node.loc.ownContains(comment))
: is_MacroRule(node)
? escapeComments(0, (comment) => node.transform.loc.contains(comment))
: is_UnionPattern(getParentNode() ?? ({ nodeType: 0 } as any))
? new Set(getComments(node, CF.Leading | CF.Trailing, (comment) => !isDangling(comment)))
: undefined
);
if (!inner_parens && needsOuterSoftbreakParens(node)) {
printed = [group(["(", indent([softline, printed]), softline, ")"])];
}
return printed;
});
function hasPrettierIgnore(node: Node) {
return (
(node as any).prettierIgnore ||
hasComment(node, CF.PrettierIgnore) ||
(hasAttributes(node) && node.attributes.some(isPrettierIgnoreAttribute))
);
}
}
export function canAttachComment(n: Node) {
return !is_Comment(n) && !isNoopExpressionStatement(n) && !is_MissingNode(n) && !is_PunctuationToken(n);
}
export const plugin: Plugin<Node> = {
languages: [
{
name: "Rust",
aliases: ["rs"],
parsers: ["jinx-rust"],
extensions: [".rs", ".rs.in"],
linguistLanguageId: 327,
vscodeLanguageIds: ["rust"],
tmScope: "source.rust",
aceMode: "rust",
codemirrorMode: "rust",
codemirrorMimeType: "text/x-rustsrc",
},
],
parsers: {
"jinx-rust": {
astFormat: "jinx-rust",
locStart: start,
locEnd: end,
parse(code: string, options: ParserOptions<Node> & Partial<CustomOptions>) {
const customOptions = options as CustomOptions;
ctx = { options: customOptions } as any;
customOptions.rsParsedFile = rs.parseFile((customOptions.originalText = code), { filepath: customOptions.filepath });
customOptions.actuallyMethodNodes = new WeakSet();
customOptions.danglingAttributes = [];
customOptions.comments = [];
transform_ast(customOptions);
const comments: AttributeOrComment[] = [];
insertNodes(comments, customOptions.comments);
insertNodes(comments, customOptions.danglingAttributes);
// @ts-expect-error
customOptions.rsParsedFile.program.comments = comments;
customOptions.commentSpans = new Map(comments.map((n) => [start(n), end(n)]));
return customOptions.rsParsedFile.program;
},
},
},
printers: {
"jinx-rust": {
preprocess: (node: Node) => (node as Program).loc?.src || node,
print(path, options, print, args) {
if (path.stack.length === 1) {
__DEV__: Narrow<CustomOptions>(options);
ctx = { path, options, print: print as any, args };
try {
const printed = genericPrint();
__DEV__: devEndCheck(printed);
return printed;
} finally {
ctx = undefined!;
}
} else if (args || ctx.args) {
const prev_args = ctx.args;
try {
ctx.args = args;
return genericPrint();
} finally {
ctx.args = prev_args;
}
} else {
return genericPrint();
}
},
hasPrettierIgnore: () => false,
willPrintOwnComments: () => true,
isBlockComment: (node: Node): boolean => {
return is_AttributeOrComment(node) && is_BlockCommentKind(node as any);
},
canAttachComment: canAttachComment,
getCommentChildNodes: getCommentChildNodes,
printComment: genericPrint,
handleComments: {
// @ts-expect-error
avoidAstMutation: true,
ownLine: handleOwnLineComment,
endOfLine: handleEndOfLineComment,
remaining: handleRemainingComment,
},
},
},
options: {},
defaultOptions: {
// default prettier (2) -> rustfmt (4)
tabWidth: 4,
// default prettier (80) -> rustfmt (100)
printWidth: 100,
},
};
function devEndCheck(printed: Doc) {
let first = false;
const comments = getAllComments();
each(comments, (comment, index) => {
if (!comment.printed) {
if (!first) (first = true), console.log(color.red(`Unprinted comments:`));
const len = 40;
const msg =
color.magenta(
(comment.marker ? `Dangling "${comment.marker}" ` : "") +
(is_Attribute(comment) ? "Attribute " : is_DocCommentAttribute(comment) ? "DocCommentAttribute" : "Comment") +
` ${index}/${comments.length}` +
color.yellow(` ${print_string(comment.loc.sliceText(0, len))}${comment.loc.len() > len ? " ..." : ""}`)
) + color.grey(`\n at ${comment.loc.url()}`);
if (globalThis.TESTS_FORMAT_DEV) exit(msg);
else console.log(msg);
setDidPrintComment(comment);
}
});
}

View File

@@ -0,0 +1,719 @@
import { DelimKind, Node, NodeType, NTMap } from "jinx-rust";
import {
getDelimChars,
hasSuffix,
is_ArrayOrTupleLiteral,
is_BlockExpression,
is_ClosureFunctionExpression,
is_Identifier,
is_IfBlockExpression,
is_LiteralNumberLike,
is_StructLiteral,
start,
} from "jinx-rust/utils";
import {
BlockLikeMacroInvocation,
CallLikeMacroInvocation,
is_BlockLikeMacroInvocation,
is_CallLikeMacroInvocation,
isTransformed,
} from "../transform";
import { exit } from "../utils/common";
import { hasComment, print_comment } from "./comments";
import { isSimpleType } from "./complexity";
import {
adjustClause,
parenthesize_if_break,
printAnnotatedPattern,
printArrayLike,
printArrowFunction,
printAssignment,
printBinaryishExpression,
printBlockBody,
printBodyOrCases,
printCallArguments,
printCallExpression,
printCondition,
printDanglingCommentsForInline,
printDeclarationTypeBounds,
printEnumBody,
printFlowControlExpression,
printGenerics_x_whereBounds,
printIfBlock,
printIfBlockCondition,
printImplTraitForType,
printLtBounds,
printLtParameters,
printMacroRules,
printMaybeBlockBody,
printMemberExpression,
printNumber,
printObject,
printParametersAndReturnType,
printRuleMatch,
printRuleTransform,
printTypeAnnotation,
printTypeArguments,
printTypeBounds,
printUnaryExpression,
printUnionPattern,
} from "./core";
import { DCM, Doc, group, hardline, ifBreak, indent, join, line, softline, willBreak } from "./external";
import { f, getOptions, getParentNode, pathCall, sg_duo, sg_single, type print } from "./plugin";
import { needsParens, stmtNeedsSemi } from "./styling";
type nPrint<T extends Node> = (print: print<T>, node: T) => Doc | never;
export const printer: { [K in NodeType]: nPrint<Extract<NTMap[K], Node>> } = {
[NodeType.MissingNode](print, node) {
return "";
},
[NodeType.SourceFile](print, node) {
return [
print.b("UTF8BOM", "\uFEFF"), //
print("shebang"),
print("program"),
];
},
[NodeType.Shebang](print, node) {
return [`#!${node.value}`, hardline];
},
[NodeType.Program](print, node) {
return printBodyOrCases(print, node);
},
[NodeType.Snippet](print, node) {
exit.never();
},
[NodeType.Identifier](print, node) {
return node.name;
},
[NodeType.Index](print, node) {
return node.name;
},
[NodeType.LbIdentifier](print, node) {
return node.name;
},
[NodeType.McIdentifier](print, node) {
return node.name;
},
[NodeType.LtIdentifier](print, node) {
return node.name;
},
[NodeType.PunctuationToken](print, node) {
return node.token;
},
[NodeType.DelimGroup](print, node) {
return node.loc.getOwnText();
},
[NodeType.Literal](print, node) {
let { value } = node;
if (is_LiteralNumberLike(node)) value = printNumber(value);
return hasSuffix(node) ? [value, print("suffix")] : value;
},
[NodeType.ItemPath](print, node) {
return [print("namespace"), "::", print("segment")];
},
[NodeType.ExpressionPath](print, node) {
return [print("namespace"), "::", print("segment")];
},
[NodeType.TypePath](print, node) {
return [print("namespace"), "::", print("segment")];
},
[NodeType.Comment](print, node) {
return print_comment(node);
},
[NodeType.DocCommentAttribute](print, node) {
return print_comment(node);
},
[NodeType.Attribute](print, node) {
return [
node.inner ? "#![" : "#[",
isTransformed(node)
? [print("segments"), printDanglingCommentsForInline(node)] //
: node.segments.loc.sliceText(1, -1).trim(),
"]",
];
},
[NodeType.MacroInvocation](print, node) {
const hasCurlyBrackets = node.segments.dk === DelimKind["{}"];
const delim = getDelimChars(node.segments);
if (node.segments.length === 0) {
return [print("callee"), "!", hasCurlyBrackets ? " " : "", delim.left, printDanglingCommentsForInline(node), delim.right];
}
if (isTransformed(node)) {
if (is_CallLikeMacroInvocation(node)) {
return [print("callee"), "!", printCallArguments(print as print<CallLikeMacroInvocation>, node)];
}
if (is_BlockLikeMacroInvocation(node)) {
return [print("callee"), "!", " ", printBlockBody(print as print<BlockLikeMacroInvocation>, node)];
}
}
let content = node.segments.loc.sliceText(1, -1);
if (content.trim().length === 0) {
content = "";
} else if (!content.includes("\n")) {
content = content.trim();
if (hasCurlyBrackets) content = " " + content + " ";
}
return [print("callee"), "!", hasCurlyBrackets ? " " : "", delim.left, content, delim.right];
},
[NodeType.MacroRulesDeclaration](print, node) {
return ["macro_rules! ", print("id"), printMacroRules(print, node)];
},
[NodeType.MacroRuleDeclaration](print, node) {
return [printRuleMatch(print, node), " => ", printRuleTransform(print, node), ";"];
},
[NodeType.MacroDeclaration](print, node) {
return [print("pub"), "macro ", print("id"), printMacroRules(print, node)];
},
[NodeType.MacroInlineRuleDeclaration](print, node) {
return [printRuleMatch(print, node), " ", printRuleTransform(print, node)];
},
[NodeType.MacroGroup](print, node) {
return node.loc.getOwnText();
},
[NodeType.MacroParameterDeclaration](print, node) {
return [print("id"), ":", print("ty")];
},
[NodeType.PubSpecifier](print, node) {
if (!node.location) return "pub ";
if (is_Identifier(node.location)) {
switch (node.location.name) {
case "crate":
if (start(node) === start(node.location)) {
return "crate ";
} else {
return ["pub(", print("location"), ") "];
}
case "self":
case "super":
return ["pub(", print("location"), ") "];
}
}
return ["pub(in ", print("location"), ") "];
},
[NodeType.ExternSpecifier](print, node) {
return ["extern ", f`${print("abi")} `];
},
[NodeType.ExpressionStatement](print, node) {
return [print("expression"), stmtNeedsSemi(node) ? ";" : ""];
},
[NodeType.UseStatement](print, node) {
return [print("pub"), "use ", print("import"), ";"];
},
[NodeType.DestructuredImport](print, node) {
if (node.specifiers.length === 0) return [print("source"), "::{", printDanglingCommentsForInline(node, DCM["specifiers"]), "}"];
let space = true;
__DEV__: if (globalThis.TESTS_FORMAT_DEV) space = false;
return [
print("source"),
group([
"::{",
indent([space ? line : softline, join([",", line], print("specifiers")), ifBreak(",")]),
space ? line : softline,
"}",
]),
];
},
[NodeType.AmbientImport](print, node) {
return f`${print("source")}::*` || "*";
},
[NodeType.AnonymousImport](print, node) {
return [print("source"), " as ", "_"];
},
[NodeType.NamedImport](print, node) {
return [print("source"), f` as ${print("local")}`];
},
[NodeType.ExternCrateStatement](print, node) {
return [print("pub"), "extern crate ", print("import"), ";"];
},
[NodeType.TypeAliasDeclaration](print, node) {
return [
print("pub"),
"type",
printAssignment(
printGenerics_x_whereBounds(print, node, printDeclarationTypeBounds(print, node, ":")), //
" =",
"typeExpression"
),
";",
];
},
[NodeType.LetVariableDeclaration](print, node) {
return [
"let ",
printAssignment(
printAnnotatedPattern(print, node), //
" =",
"expression"
),
f` else ${print("else")}`,
";",
];
},
[NodeType.ConstVariableDeclaration](print, node) {
return [
print("pub"),
"const ",
printAssignment(
printAnnotatedPattern(print, node), //
" =",
"expression"
),
";",
];
},
[NodeType.StaticVariableDeclaration](print, node) {
return [
print("pub"),
"static ",
printAssignment(
printAnnotatedPattern(print, node), //
" =",
"expression"
),
";",
];
},
[NodeType.ModuleDeclaration](print, node) {
return [
print("pub"), //
print.b("unsafe"),
"mod ",
print("id"),
printMaybeBlockBody(print, node),
];
},
[NodeType.ExternBlockDeclaration](print, node) {
return [
print("pub"), //
print.b("unsafe"),
"extern ",
f`${print("abi")} `,
printBlockBody(print, node),
];
},
[NodeType.FunctionDeclaration](print, node) {
return [
print("pub"),
print.b("const"),
print.b("async"),
print.b("unsafe"),
print("extern"),
"fn",
printGenerics_x_whereBounds(print, node, printParametersAndReturnType(node)),
printMaybeBlockBody(print, node),
];
},
[NodeType.FunctionSelfParameterDeclaration](print, node) {
return group([print.b("ref", "&"), f`${print("lt")} `, print.b("mut"), "self", printTypeAnnotation(print, node)]);
},
[NodeType.FunctionParameterDeclaration](print, node) {
return group(printAnnotatedPattern(print, node));
},
[NodeType.FunctionSpread](print, node) {
return "...";
},
[NodeType.StructDeclaration](print, node) {
return [print("pub"), "struct", printGenerics_x_whereBounds(print, node, ""), printObject(print, node)];
},
[NodeType.StructPropertyDeclaration](print, node) {
return [print("pub"), print("id"), printTypeAnnotation(print, node)];
},
[NodeType.TupleStructDeclaration](print, node) {
return [print("pub"), "struct", printGenerics_x_whereBounds(print, node, printArrayLike(print, node)), ";"];
},
[NodeType.TupleStructItemDeclaration](print, node) {
return [print("pub"), print("typeAnnotation")];
},
[NodeType.UnionDeclaration](print, node) {
return [print("pub"), "union", printGenerics_x_whereBounds(print, node, ""), printObject(print, node)];
},
[NodeType.EnumDeclaration](print, node) {
return [print("pub"), "enum", printGenerics_x_whereBounds(print, node, ""), printEnumBody(print, node)];
},
[NodeType.EnumMemberDeclaration](print, node) {
return [
print("pub"),
printAssignment(
print("id"), //
" =",
"value"
),
];
},
[NodeType.EnumMemberTupleDeclaration](print, node) {
return [
print("pub"),
printAssignment(
[print("id"), printArrayLike(print, node)], //
" =",
"value"
),
];
},
[NodeType.EnumMemberStructDeclaration](print, node) {
return [
print("pub"),
printAssignment(
[print("id"), printObject(print, node)], //
" =",
"value"
),
];
},
[NodeType.TraitDeclaration](print, node) {
return [
print("pub"),
print.b("unsafe"),
"trait",
printGenerics_x_whereBounds(print, node, printDeclarationTypeBounds(print, node, ":")),
adjustClause(node, printBlockBody(print, node)),
];
},
[NodeType.AutoTraitDeclaration](print, node) {
return [
print("pub"),
print.b("unsafe"),
"auto trait ",
print("id"),
" ",
printBlockBody(print, node as any), // see "transform.ts"
];
},
[NodeType.TraitAliasDeclaration](print, node) {
return [
print("pub"),
print.b("unsafe"),
"trait",
printGenerics_x_whereBounds(print, node, printDeclarationTypeBounds(print, node, " =")),
";",
];
},
[NodeType.ImplDeclaration](print, node) {
return [
print("pub"),
print.b("unsafe"),
"impl",
printGenerics_x_whereBounds(print, node, [print.b("const"), printImplTraitForType(print, node)]),
adjustClause(node, printBlockBody(print, node)),
];
},
[NodeType.NegativeImplDeclaration](print, node) {
return [
print("pub"),
"impl",
printGenerics_x_whereBounds(print, node, ["!", printImplTraitForType(print, node)]),
" ",
printBlockBody(print, node as any), // see "transform.ts"
];
},
[NodeType.ExpressionTypeSelector](print, node) {
return group(["<", print("typeTarget"), f` as ${print("typeExpression")}`, ">"]);
},
[NodeType.ExpressionTypeCast](print, node) {
return [print("typeCallee"), f`::${printTypeArguments(print, node)}`];
},
[NodeType.ExpressionAsTypeCast](print, node) {
return [print("expression"), " as ", print("typeExpression")];
},
[NodeType.ReturnExpression](print, node) {
return ["return", printFlowControlExpression(print, node)];
},
[NodeType.BreakExpression](print, node) {
return ["break", f` ${print("label")}`, printFlowControlExpression(print, node)];
},
[NodeType.ContinueExpression](print, node) {
return ["continue", f` ${print("label")}`];
},
[NodeType.YieldExpression](print, node) {
return ["yield", printFlowControlExpression(print, node)];
},
[NodeType.RangeLiteral](print, node) {
return [print("lower"), "..", print.b("last", "="), print("upper")];
},
[NodeType.CallExpression](print, node) {
return printCallExpression(print, node);
},
[NodeType.MemberExpression](print, node) {
return printMemberExpression(print, node);
},
[NodeType.AwaitExpression](print, node) {
return [print("expression"), ".await"];
},
[NodeType.UnwrapExpression](print, node) {
return [print("expression"), "?"];
},
[NodeType.ParenthesizedExpression](print, node) {
exit.never();
const shouldHug = !hasComment(node.expression) && (is_ArrayOrTupleLiteral(node.expression) || is_StructLiteral(node.expression));
if (shouldHug) return ["(", print("expression"), ")"];
return group(["(", indent([softline, print("expression")]), softline, ")"]);
},
[NodeType.MinusExpression](print, node) {
return printUnaryExpression("-", node);
},
[NodeType.NotExpression](print, node) {
return printUnaryExpression("!", node);
},
[NodeType.OrExpression](print, node) {
return printBinaryishExpression(print, node);
},
[NodeType.AndExpression](print, node) {
return printBinaryishExpression(print, node);
},
[NodeType.ReassignmentExpression](print, node) {
return printAssignment(print("left"), " =", "right");
},
[NodeType.UnassignedExpression](print, node) {
return "_";
},
[NodeType.OperationExpression](print, node) {
return printBinaryishExpression(print, node);
},
[NodeType.ReassignmentOperationExpression](print, node) {
return printAssignment(print("left"), " " + node.kind, "right");
},
[NodeType.ComparisonExpression](print, node) {
return printBinaryishExpression(print, node);
},
[NodeType.LetScrutinee](print, node) {
return ["let ", printAssignment(print("pattern"), " =", "expression")];
},
[NodeType.ClosureFunctionExpression](print, node) {
return printArrowFunction(print, node);
},
[NodeType.ClosureFunctionParameterDeclaration](print, node) {
return group(printAnnotatedPattern(print, node));
},
[NodeType.BlockExpression](print, node) {
return [
f`${print("label")}: `,
print.b("const"),
print.b("async"),
print.b("move"),
print.b("unsafe"),
printBlockBody(print, node),
];
},
[NodeType.LoopBlockExpression](print, node) {
return [f`${print("label")}: `, "loop ", printBlockBody(print, node)];
},
[NodeType.WhileBlockExpression](print, node) {
return [f`${print("label")}: `, "while ", printCondition(print, node), printBlockBody(print, node)];
},
[NodeType.ForInBlockExpression](print, node) {
return [f`${print("label")}: `, "for ", print("pattern"), " in ", print("expression"), " ", printBlockBody(print, node)];
},
[NodeType.IfBlockExpression](print, node) {
return [f`${print("label")}: `, printIfBlock(print, node)];
},
[NodeType.TryBlockExpression](print, node) {
return [f`${print("label")}: `, "try ", printBlockBody(print, node)];
},
[NodeType.MatchExpression](print, node) {
const id = Symbol("match");
const expr = print("expression");
const needs_parens = pathCall(node, "expression", needsParens);
let printed: Doc = [
f`${print("label")}: `,
"match ",
needs_parens ? expr : group([indent([softline, expr]), softline], { id }),
needs_parens ? " " : !willBreak(expr) ? ifBreak("", " ", { groupId: id }) : "" /* ifBreak("", " ", { groupId: id }) */,
printBlockBody(print, node),
];
const parent = getParentNode()!;
if (is_ClosureFunctionExpression(parent) && parent.expression === node) {
printed = parenthesize_if_break([indent([softline, printed]), softline]);
}
return printed;
},
[NodeType.MatchExpressionCase](print, node) {
return group([
group(print("pattern")),
" ",
printIfBlockCondition(print, node),
"=>", //
(is_BlockExpression(node.expression) || is_IfBlockExpression(node.expression)) &&
!hasComment(node.expression, 0, (comment) => getOptions().danglingAttributes.includes(comment as any))
? [" ", print("expression")]
: group(indent([line, print("expression")])),
]);
return printAssignment(
[print("pattern"), " ", printIfBlockCondition(print, node)], //
"=>",
"expression"
);
return [print("pattern"), " ", printIfBlockCondition(print, node)];
},
[NodeType.StructLiteral](print, node) {
return [print("struct"), printObject(print, node)];
},
[NodeType.StructLiteralPropertyShorthand](print, node) {
return print("value");
},
[NodeType.StructLiteralProperty](print, node) {
return [print("key"), ": ", print("value")];
},
[NodeType.StructLiteralPropertySpread](print, node) {
return ["..", print("expression")];
},
[NodeType.StructLiteralRestUnassigned](print, node) {
return "..";
},
[NodeType.ArrayLiteral](print, node) {
return printArrayLike(print, node);
},
[NodeType.SizedArrayLiteral](print, node) {
return sg_duo`[${print("initExpression")};${print("sizeExpression")}]`;
},
[NodeType.TupleLiteral](print, node) {
return printArrayLike(print, node);
},
[NodeType.ReferenceExpression](print, node) {
return printUnaryExpression(["&", print.b("mut")], node);
},
[NodeType.RawReferenceExpression](print, node) {
return printUnaryExpression(`&raw ${node.kind} `, node);
},
[NodeType.DereferenceExpression](print, node) {
return printUnaryExpression("*", node);
},
[NodeType.BoxExpression](print, node) {
return printUnaryExpression("box ", node);
},
[NodeType.UnionPattern](print, node) {
return printUnionPattern(print, node);
},
[NodeType.ParenthesizedPattern](print, node) {
exit.never();
return sg_single`(${print("pattern")})`;
},
[NodeType.RestPattern](print, node) {
return "..";
},
[NodeType.WildcardPattern](print, node) {
return "_";
},
[NodeType.PatternVariableDeclaration](print, node) {
return [print.b("ref"), print.b("mut"), printAssignment(print("id"), " @", "pattern")];
},
[NodeType.StructPattern](print, node) {
return [print("struct"), printObject(print, node)];
},
[NodeType.StructPatternPropertyDestructured](print, node) {
return [print("key"), ": ", print("pattern")];
},
[NodeType.StructPatternPropertyShorthand](print, node) {
return [print.b("box"), print.b("ref"), print.b("mut"), print("id")];
},
[NodeType.TuplePattern](print, node) {
return [print("struct"), printArrayLike(print, node)];
},
[NodeType.ArrayPattern](print, node) {
return printArrayLike(print, node);
},
[NodeType.ReferencePattern](print, node) {
return ["&", print.b("mut"), print("pattern")];
},
[NodeType.BoxPattern](print, node) {
return ["box ", print("pattern")];
},
[NodeType.MinusPattern](print, node) {
return ["-", print("pattern")];
},
[NodeType.RangePattern](print, node) {
return [print("lower"), "..", print.b("last", "="), print("upper")];
},
[NodeType.TypeCall](print, node) {
return [print("typeCallee"), printTypeArguments(print, node)];
},
[NodeType.TypeCallNamedArgument](print, node) {
return printAssignment(print("target"), " =", "typeExpression");
},
[NodeType.TypeCallNamedBound](print, node) {
return [print("typeTarget"), printTypeBounds(":", print, node)];
},
[NodeType.LtElided](print, node) {
return "'_";
},
[NodeType.LtStatic](print, node) {
return "'static";
},
[NodeType.TypeNever](print, node) {
return "!";
},
[NodeType.TypeInferred](print, node) {
return "_";
},
[NodeType.GenericTypeParameterDeclaration](print, node) {
return printAssignment(
[print("id"), printTypeBounds(":", print, node)], //
" =",
"typeDefault"
);
},
[NodeType.ConstTypeParameterDeclaration](print, node) {
return [
"const ",
printAssignment(
[print("id"), printTypeAnnotation(print, node)], //
" =",
"typeDefault"
),
];
},
[NodeType.GenericLtParameterDeclaration](print, node) {
return [print("id"), printLtBounds(":", print, node)];
},
[NodeType.WhereTypeBoundDeclaration](print, node) {
return [printLtParameters(print, node), print("typeTarget"), printTypeBounds(":", print, node)];
},
[NodeType.WhereLtBoundDeclaration](print, node) {
return [print("ltTarget"), printLtBounds(":", print, node)];
},
[NodeType.TypeTraitBound](print, node) {
return [print.b("maybeConst", "~const "), print.b("optional", "?"), printLtParameters(print, node), print("typeExpression")];
},
[NodeType.TypeDynBounds](print, node) {
return printTypeBounds("dyn", print, node);
},
[NodeType.TypeImplBounds](print, node) {
return printTypeBounds("impl", print, node);
},
[NodeType.TypeFnPointer](print, node) {
return [printLtParameters(print, node), print.b("unsafe"), print("extern"), "fn", printParametersAndReturnType(node)];
},
[NodeType.TypeFnPointerParameter](print, node) {
return [f`${print("id")}: `, print("typeAnnotation")];
},
[NodeType.TypeFunction](print, node) {
return [print("callee"), printParametersAndReturnType(node)];
},
[NodeType.TypeTuple](print, node) {
return printArrayLike(print, node);
},
[NodeType.TypeSizedArray](print, node) {
return sg_duo`[${print("typeExpression")};${print("sizeExpression")}]`;
if (isSimpleType(node)) return ["[", print("typeExpression"), "; ", print("sizeExpression"), "]"];
},
[NodeType.TypeSlice](print, node) {
if (isSimpleType(node)) return ["[", print("typeExpression"), "]"];
return sg_single`[${print("typeExpression")}]`;
},
[NodeType.TypeReference](print, node) {
return ["&", f`${print("lt")} `, print.b("mut"), print("typeExpression")];
},
[NodeType.TypeDereferenceConst](print, node) {
return ["*const ", print("typeExpression")];
},
[NodeType.TypeDereferenceMut](print, node) {
return ["*mut ", print("typeExpression")];
},
[NodeType.TypeParenthesized](print, node) {
exit.never();
return sg_single`(${print("typeExpression")})`;
},
};

View File

@@ -0,0 +1,645 @@
import {
ClosureFunctionExpression,
ComparisonExpression,
ConditionExpression,
EnumDeclaration,
EnumMemberStructDeclaration,
ExpressionAsTypeCast,
ExpressionNode,
ExpressionStatement,
ExpressionWithBody,
LeftRightExpression,
LogicalExpression,
MacroDeclaration,
MacroRulesDeclaration,
MissingNode,
Node,
NodeType,
NodeWithBody,
NodeWithBodyOrCases,
OperationExpression,
PRCD,
StructDeclaration,
StructLiteral,
StructPattern,
TK,
UnionDeclaration,
} from "jinx-rust";
import {
can_have_OuterAttributes,
getAstPath,
getPrecedence,
hasAttributes,
hasBody,
hasCondition,
hasItems,
hasLetScrutineeCondition,
hasOuterAttributes,
hasProperties,
is_Attribute,
is_AttributeOrDocComment,
is_AwaitExpression,
is_BitwiseOperator,
is_CallExpression,
is_ClosureFunctionExpression,
is_ComparisonExpression,
is_DocCommentAttribute,
is_ElseBlock,
is_EnumMemberDeclaration,
is_EqualityOperator,
is_ExpressionAsTypeCast,
is_ExpressionStatement,
is_ExpressionWithBody,
is_ExpressionWithBodyOrCases,
is_ExpressionWithBodyOrCases_or_BlockLikeMacroInvocation,
is_FlowControlExpression,
is_FlowControlMaybeValueExpression,
is_ForInBlockExpression,
is_Identifier,
is_IfBlockExpression,
is_ImplicitReturnAbleNode,
is_LeftRightExpression,
is_LetScrutinee,
is_Literal,
is_LiteralNumberLike,
is_LogicalExpression,
is_LoopBlockExpression,
is_MatchExpression,
is_MatchExpressionCase,
is_MemberExpression,
is_NodeWithBodyNoBody,
is_NodeWithMaybePatternNoUnionBody,
is_OperationExpression,
is_ParenthesizedNode,
is_PatternVariableDeclaration,
is_PostfixExpression,
is_RangeLiteral,
is_ReassignmentNode,
is_ReturnExpression,
is_StatementNode,
is_StructLiteral,
is_StructLiteralProperty,
is_StructPatternProperty,
is_StructPropertyDeclaration,
is_TypeBoundsStandaloneNode,
is_TypeFunctionNode,
is_TypeTraitBound,
is_UnaryExpression,
is_UnaryType,
is_UnionPattern,
is_WhileBlockExpression,
is_YieldExpression,
is_bitshiftOperator,
is_multiplicativeOperator,
} from "jinx-rust/utils";
import { BlockLikeMacroInvocation, is_BlockLikeMacroInvocation, is_CallExpression_or_CallLikeMacroInvocation } from "../transform";
import { exit, last_of } from "../utils/common";
import { CF, hasBreaklineAfter, hasComment } from "./comments";
import { flowControlExpressionNeedsOuterParens } from "./core";
import { Doc, hardline, softline, willBreak } from "./external";
import {
assertPathAtNode,
getContext,
getGrandParentNode,
getNode,
getOptions,
getParentNode,
getPrintFn,
is_printing_macro,
pathCallAtParent,
pathCallParentOf,
stackIncludes,
} from "./plugin";
export function needsOuterSoftbreakParens(node: Node) {
const parent = getParentNode(node);
if (!parent) return false;
if (is_ExpressionAsTypeCast(node)) {
return precedenceNeedsParens(node, parent);
}
if (
is_FlowControlMaybeValueExpression(parent) && //
parent.expression === node &&
flowControlExpressionNeedsOuterParens(parent)
) {
return true;
}
if (
is_ExpressionWithBodyOrCases_or_BlockLikeMacroInvocation(node) &&
(false ||
(is_MemberExpression(parent) && parent.expression === node) ||
(is_ExpressionWithBodyOrCases_or_BlockLikeMacroInvocation(parent) && !is_ElseBlock(node, parent)))
) {
return true;
}
if (is_UnionPattern(node) && is_NodeWithMaybePatternNoUnionBody(parent)) {
return true;
}
if (hasComment(node)) {
if (is_UnaryExpression(parent)) {
return true;
}
if (hasComment(node, CF.Line)) {
if (is_ReturnExpression(parent) || (is_YieldExpression(parent) && parent.expression === node)) {
return true;
}
}
if (
hasComment(node, CF.Leading, (comment) => is_Attribute(comment) && !comment.inner) &&
!can_have_OuterAttributes(node, parent, true)
) {
return true;
}
}
return false;
}
export function needsInnerParens(node: Node) {
if (needsOuterSoftbreakParens(node)) {
return false;
}
const parent = getParentNode(node);
if (!parent) {
return false;
}
if (is_Identifier(node)) {
return false;
}
if (is_Literal(node)) {
return is_LiteralNumberLike(node) && is_MemberExpression(parent) && node === parent.expression;
}
if (is_CallExpression(parent) && parent.callee === node && is_MemberExpression(node)) {
return !getOptions().actuallyMethodNodes.has(node);
}
if (is_ReassignmentNode(node)) {
if (is_printing_macro()) {
return false;
}
if (is_ClosureFunctionExpression(parent) && node === parent.expression) {
return true;
}
if (is_ExpressionStatement(parent)) {
return is_StructLiteral(node.left);
}
if (is_ReassignmentNode(parent)) {
return false;
}
return true;
}
if (is_ParenthesizedNode(parent)) {
return false;
}
if (is_ExpressionStatement(parent)) {
return false;
}
if (is_RangeLiteral(node)) {
return (
is_ExpressionAsTypeCast(parent) ||
is_LogicalExpression(parent) ||
is_UnaryExpression(parent) ||
is_PostfixExpression(parent) ||
(is_MemberExpression(parent) && node === parent.expression) ||
(is_CallExpression(parent) && node === parent.callee) ||
is_OperationExpression(parent) ||
is_ComparisonExpression(parent)
);
}
if (is_LetScrutinee(parent) && is_LogicalExpression(node) && parent.expression === (node as any)) {
return true;
}
if (is_UnaryExpression(node)) {
switch (parent.nodeType) {
case NodeType.MemberExpression:
case NodeType.AwaitExpression:
return node === parent.expression;
case NodeType.CallExpression:
return node === parent.callee;
default:
return false;
}
}
if (is_ExpressionWithBodyOrCases_or_BlockLikeMacroInvocation(node)) {
if (is_ExpressionWithBodyOrCases(parent)) {
return !is_ElseBlock(node, parent);
}
if (is_LetScrutinee(parent) && parent.expression === node && is_ExpressionWithBodyOrCases(getGrandParentNode())) {
return true;
}
return (
is_ExpressionAsTypeCast(parent) ||
is_LogicalExpression(parent) ||
is_UnaryExpression(parent) ||
is_PostfixExpression(parent) ||
(is_MemberExpression(parent) && node === parent.expression) ||
(is_CallExpression(parent) && node === parent.callee) ||
is_OperationExpression(parent) ||
is_ComparisonExpression(parent) ||
is_RangeLiteral(parent)
);
}
if (is_StructLiteral(node)) {
if (is_ExpressionWithBodyOrCases(parent)) {
return true;
}
if (is_LetScrutinee(parent) && parent.expression === node && is_ExpressionWithBodyOrCases(getGrandParentNode())) {
return true;
}
if (is_UnaryExpression(parent) || is_PostfixExpression(parent) || is_MemberExpression(parent)) {
return parent.expression === node;
}
if (is_CallExpression(parent)) {
return parent.callee === node;
}
}
if (is_LogicalExpression(node) || is_OperationExpression(node) || is_ComparisonExpression(node) || is_ClosureFunctionExpression(node)) {
return precedenceNeedsParens(node, parent);
}
if (is_TypeFunctionNode(node)) {
const gp = getGrandParentNode();
if (node.returnType && is_TypeTraitBound(parent) && is_TypeBoundsStandaloneNode(gp) && last_of(gp.typeBounds) !== parent) {
return true;
}
}
if (is_TypeBoundsStandaloneNode(node)) {
return (
(is_UnaryType(parent) && node.typeBounds.length > 1) ||
is_TypeBoundsStandaloneNode(parent) ||
is_TypeTraitBound(parent) ||
(is_TypeFunctionNode(parent) && parent.returnType === node)
);
}
if (is_PatternVariableDeclaration(parent)) {
return is_UnionPattern(node);
}
return false;
}
function precedenceNeedsParens(node: LeftRightExpression | ClosureFunctionExpression | ExpressionAsTypeCast, parent: Node) {
if (is_UnaryExpression(parent) || is_PostfixExpression(parent)) return true;
if (is_ReassignmentNode(parent)) return parent.left === node;
if (is_MemberExpression(parent)) return parent.expression === node;
if (is_CallExpression(parent)) return parent.callee === node;
if (is_ExpressionAsTypeCast(parent)) return !is_ExpressionAsTypeCast(node);
if (is_LogicalExpression(parent)) return is_LogicalExpression(node) ? parent.nodeType !== node.nodeType : evalPrecedence(node, parent);
if (is_OperationExpression(parent) || is_ComparisonExpression(parent)) return evalPrecedence(node, parent);
return false;
function evalPrecedence(
child: LeftRightExpression | ClosureFunctionExpression | ExpressionAsTypeCast,
parent: ComparisonExpression | OperationExpression | LogicalExpression
) {
if (is_ExpressionAsTypeCast(child) || is_ClosureFunctionExpression(child)) {
return true;
}
function getPrec(node, bool) {
// if (is_EqualityOperator(node.tk)) {
// return 11.3;
// }
// if (is_LargerLesserOperator(node.tk)) {
// return 11.6;
// }
return getPrecedence(node, bool);
}
const childPRCD = getPrec(child, is_insideScrutinee(child));
const parentPRCD = getPrec(parent, is_insideScrutinee(parent));
if (parentPRCD > childPRCD) {
return true;
}
if (parentPRCD === childPRCD && parent.right === child) {
return true;
}
if (parentPRCD === childPRCD && !shouldFlatten(parent, child)) {
return true;
}
if (parentPRCD < childPRCD && child.tk === TK["%"]) {
return parentPRCD === PRCD["+-"];
}
if (is_BitwiseOperator(parent.tk) || (is_BitwiseOperator(child.tk) && is_EqualityOperator(parent.tk))) {
return true;
}
return false;
}
}
export function shouldFlatten(parent: ExpressionNode | ConditionExpression, node: ExpressionNode | ConditionExpression) {
if (getPrecedence(node, is_insideScrutinee(node)) !== getPrecedence(parent, is_insideScrutinee(parent))) return false;
if (is_ComparisonExpression(parent) && is_ComparisonExpression(node)) return false;
if (is_OperationExpression(parent) && is_OperationExpression(node)) {
if (
(node.tk === TK["%"] && is_multiplicativeOperator(parent.tk)) ||
(parent.tk === TK["%"] && is_multiplicativeOperator(node.tk)) ||
(node.tk !== parent.tk && is_multiplicativeOperator(node.tk) && is_multiplicativeOperator(parent.tk)) ||
(is_bitshiftOperator(node.tk) && is_bitshiftOperator(parent.tk))
)
return false;
}
return true;
}
export function needsParens(node: Node) {
return needsOuterSoftbreakParens(node) || needsInnerParens(node);
}
export function stmtNeedsSemi(stmt: ExpressionStatement, disregardExprType = false) {
return pathCallParentOf(stmt, (parent) => needsSemi(parent as any, stmt, disregardExprType));
}
const NoNode = { nodeType: 0 } as MissingNode;
export function needsSemi(parent: NodeWithBody, stmt: ExpressionStatement, disregardExprType = false) {
const expr = disregardExprType ? NoNode : stmt.expression!;
const hadSemi = !disregardExprType && stmt.semi;
return (
!!expr &&
(forcePreserveSemi()
? true
: shouldNeverSemi()
? false
: shouldPreserveSemi()
? hadSemi || shouldAlwaysSemi() || canAutoCompleteSemi()
: true)
);
function forcePreserveSemi() {
/** Rust Compiler bug (preserve optional semicolon) */
// rust-lang/rust#70844 https://github.com/rust-lang/rust/issues/70844
// issue#22 https://github.com/jinxdash/prettier-plugin-rust/issues/22
return (
hadSemi &&
stmt === last_of(parent.body!) &&
((is_IfBlockExpression(expr) &&
hasLetScrutineeCondition(expr) &&
!(is_LetScrutinee(expr.condition) && is_Identifier(expr.condition.expression))) ||
(is_MatchExpression(expr) && !is_Identifier(expr.expression)))
);
}
function shouldNeverSemi() {
return is_ExpressionWithBodyOrCases_or_BlockLikeMacroInvocation(expr);
}
function shouldPreserveSemi() {
return stmt === last_of(parent.body!) && (is_ImplicitReturnAbleNode(parent) || is_BlockLikeMacroInvocation(parent));
}
function shouldAlwaysSemi() {
return is_FlowControlExpression(expr) || is_ReassignmentNode(expr);
}
function canAutoCompleteSemi() {
return withPathAt(parent, function checkParent(child: NodeWithBodyOrCases): boolean {
return pathCallParentOf(child, (parent) => {
if (is_IfBlockExpression(parent) && parent.else === child) {
// if ... { ... } else if { ... } ...
// ^ ------------------------------- parent
// ^ ----------- child
return checkParent(parent);
}
if (is_ExpressionStatement(parent)) {
// { .... { ... } ... }
// ^ -----------------^ grandparent
// ^ --- ^ ExpressionStatement<child>
if (hasOuterAttributes(parent)) return false;
return stmtNeedsSemi(parent, true);
}
if (is_MatchExpressionCase(parent) && parent.expression === child) {
return pathCallParentOf(parent, checkParent);
}
return false;
});
});
}
}
export function canInlineBlockBody(node: NodeWithBodyOrCases | BlockLikeMacroInvocation): boolean {
if (!is_ExpressionWithBody(node)) {
return false;
}
const body = node.body;
if (body.length === 0) {
return canInlineInlineable(node);
}
if (body.length === 1) {
const stmt = body[0];
if (is_AttributeOrDocComment(stmt)) {
return true;
}
if (is_ExpressionStatement(stmt) && !needsSemi(node, stmt)) {
/**
* parent ( ExpressionStatement | StructLiteralProperty | LetVariableDeclaration | ... )
* ...
* node {
* expr
* }
* ...
*
*
* Q: Can you inline "node { expr }" ?
*/
const expr = stmt.expression!;
if (
is_FlowControlExpression(expr) || //
is_ClosureFunctionExpression(expr) ||
is_ExpressionWithBodyOrCases_or_BlockLikeMacroInvocation(expr)
) {
return false;
}
return canInlineInlineable(node);
}
}
return false;
}
// function q(node: ExpressionWithBody) {
// pathCallTopMostIfBlockExpression(node, (node) => {});
// }
function canInlineInlineable(node: ExpressionWithBody) {
if (is_ForInBlockExpression(node) || is_LoopBlockExpression(node)) {
return false;
}
if (is_WhileBlockExpression(node)) {
return true;
}
const parent = getParentNode(node)!;
if (
is_ExpressionStatement(parent) &&
(!is_ImplicitReturnAbleNode(node) || pathCallAtParent(parent, (parent) => stmtNeedsSemi(parent, true)))
) {
return false;
}
if (is_ElseBlock(node, parent)) {
return pathCallAtParent(parent, canInlineBlockBody);
}
// if (is_CaseBlock(node, parent)) {
// return false;
// }
if (is_IfBlockExpression(node)) {
if (
!node.else ||
// hasLetScrutineeCondition(node) ||
is_ExpressionWithBodyOrCases_or_BlockLikeMacroInvocation(node.condition) ||
willBreak(getPrintFn(node)("condition"))
) {
return false;
}
const grandparent = getGrandParentNode();
if (is_ExpressionStatement(parent) && hasBody(grandparent) && grandparent.body.length > 1) {
return false;
}
}
return true;
return (
is_CallExpression_or_CallLikeMacroInvocation(parent) ||
hasItems(parent) ||
hasProperties(parent) ||
is_ClosureFunctionExpression(parent) ||
is_MemberExpression(parent) ||
is_AwaitExpression(parent) ||
is_LeftRightExpression(parent)
);
}
type NodeWithBracketContent =
| NodeWithBodyOrCases
| BlockLikeMacroInvocation
| EnumDeclaration
| StructDeclaration
| StructLiteral
| StructPattern
| EnumMemberStructDeclaration
| UnionDeclaration
| MacroRulesDeclaration
| MacroDeclaration;
export function emptyContent(node: NodeWithBracketContent): Doc {
switch (node.nodeType) {
case NodeType.Program:
case NodeType.MacroRulesDeclaration:
case NodeType.MacroDeclaration:
case NodeType.ExternBlockDeclaration:
case NodeType.ModuleDeclaration:
case NodeType.TraitDeclaration:
case NodeType.StructDeclaration:
case NodeType.MacroInvocation:
case NodeType.FunctionDeclaration:
case NodeType.ImplDeclaration:
case NodeType.UnionDeclaration:
case NodeType.EnumDeclaration:
case NodeType.EnumMemberStructDeclaration:
case NodeType.StructLiteral:
case NodeType.StructPattern:
// case NodeType.MatchExpression:
return "";
case NodeType.BlockExpression:
case NodeType.WhileBlockExpression:
case NodeType.ForInBlockExpression:
case NodeType.TryBlockExpression:
case NodeType.IfBlockExpression:
return canInlineInlineable(node)
? is_IfBlockExpression(node) || is_ElseBlock(node, getParentNode()!)
? softline
: ""
: hardline;
case NodeType.LoopBlockExpression:
case NodeType.MatchExpression:
return hardline;
default:
if (is_NodeWithBodyNoBody(node)) {
return "";
}
__DEV__: exit.never(node);
return "";
}
}
export function is_insideScrutinee(target: Node) {
return withPathAt(target, (n) => stackIncludes("condition") && r(n));
function r(CHILD: Node) {
switch (CHILD.nodeType) {
case NodeType.OrExpression:
case NodeType.AndExpression:
return pathCallParentOf(CHILD, (PARENT) =>
hasCondition(PARENT) && PARENT.condition === CHILD //
? hasLetScrutineeCondition(PARENT)
: r(PARENT)
);
case NodeType.LetScrutinee:
return true;
default:
return false;
}
}
}
function withPathAt<T extends Node, R>(target: T, callback: (target: T) => R): R {
if (target === getNode()) return callback(target);
if (target === getParentNode()) return pathCallAtParent(target, () => callback(target));
if (stackIncludes(target)) return pathCallAtParent(getParentNode()!, () => withPathAt(target, callback));
return getContext().path.call(() => {
__DEV__: assertPathAtNode("withPathAt", target);
return callback(target); // @ts-ignore
}, ...getAstPath(getNode(), target));
}
export function shouldPrintOuterAttributesAbove(node: Node) {
return (
is_StatementNode(node) ||
is_MatchExpressionCase(node) ||
(hasAttributes(node) &&
node.attributes.some(
canInlineOuterAttribute(node)
? (attr) => is_DocCommentAttribute(attr) || hasBreaklineAfter(attr) //
: is_DocCommentAttribute
))
);
function canInlineOuterAttribute(node: Node) {
return (
is_EnumMemberDeclaration(node) ||
is_StructPropertyDeclaration(node) ||
is_StructLiteralProperty(node) ||
is_StructPatternProperty(node)
);
}
}

View File

@@ -0,0 +1,8 @@
import { plugin } from "./format/plugin";
export default plugin;
export const languages = plugin.languages;
export const parsers = plugin.parsers;
export const printers = plugin.printers;
export const options = plugin.options;
export const defaultOptions = plugin.defaultOptions;

View File

@@ -0,0 +1,116 @@
import {
AttrSegment,
CallExpression,
DelimKind,
ExpressionPath,
Identifier,
Literal,
LocArray,
MacroInvocation,
NodeType,
PunctuationToken,
ReassignmentExpression,
TK,
rs,
} from "jinx-rust";
import { isTK, start } from "jinx-rust/utils";
import { assert, exit } from "../../utils/common";
import { isIdent } from "./utils";
type SimpleAttrItem =
| Identifier //
| Literal
| ExpressionPath
| CallExpression
| ReassignmentExpression
| MacroInvocation;
export function transform_simpleAttrSyntax(segments: MacroInvocation["segments"]) {
assert(segments.length !== 0, segments.loc.url());
return transform_segments(segments, false);
function transform_segments<N extends boolean>(
seq: LocArray<AttrSegment>,
nestedCall: N
): N extends true ? LocArray<SimpleAttrItem, "()"> : SimpleAttrItem {
let i = 0;
if (nestedCall) {
const args = rs.createLocArray<SimpleAttrItem, any>(DelimKind["()"], seq.loc.clone());
while (i !== seq.length) {
args.push(read(true));
if (i === seq.length) break;
assert(isTK(seq[i++], TK[","]));
}
return args as any;
} else {
const res = read(true);
assert(i === seq.length, res.loc.url());
return res as any;
}
function read(allowEq: boolean): SimpleAttrItem {
let lhs: Identifier | ExpressionPath;
switch (seq[i].nodeType) {
case NodeType.Literal:
return seq[i++] as Literal;
case NodeType.Identifier:
lhs = seq[i++] as Identifier;
break;
case NodeType.PunctuationToken:
assert((seq[i] as PunctuationToken).tk === TK["::"], seq[i].loc.url());
lhs = eatPathSegment(undefined);
break;
default:
exit.never();
}
while (true) {
if (i === seq.length) return lhs;
const seg = seq[i];
switch (seg.nodeType) {
case NodeType.PunctuationToken:
switch (seg.tk) {
case TK[","]:
assert(nestedCall);
return lhs;
case TK["="]: {
assert(allowEq);
const right = (i++, read(false));
return rs.mockNode(NodeType.ReassignmentExpression, right.loc.cloneFrom(start(lhs)), {
tk: TK["="],
kind: DelimKind["="],
left: lhs,
right,
});
}
case TK["::"]:
lhs = eatPathSegment(lhs);
continue;
default:
exit.never();
}
case NodeType.DelimGroup:
assert(seg.segments.dk === DelimKind["()"]);
return rs.mockNode(NodeType.CallExpression, seq[i++].loc.cloneFrom(start(lhs)), {
callee: lhs,
typeArguments: undefined,
method: undefined,
arguments: transform_segments(seg.segments, true),
});
default:
exit.never();
}
}
}
function eatPathSegment(left: undefined | Identifier | ExpressionPath) {
const segment = seq[i + 1];
assert(isIdent(segment));
const res = rs.mockNode(NodeType.ExpressionPath, segment.loc.cloneFrom(start(left ?? seq[i])), { namespace: left, segment });
i += 2;
return res;
}
}
}

View File

@@ -0,0 +1,92 @@
import {
DelimGroup,
DelimKind,
IfBlockExpression,
LocArray,
MacroInvocation,
NodeType,
NodeWithBody,
rs,
Segment,
Snippet,
StatementNode,
TK,
} from "jinx-rust";
import { insertNodes, start, transferAttributes } from "jinx-rust/utils";
import { assert, iLast } from "../../utils/common";
import { isGroup, isIdent, isToken } from "./utils";
export function transform_macro_cfg_if(segments: MacroInvocation["segments"]) {
const danglingAttributes: Snippet["danglingAttributes"] = [];
const comments: Snippet["comments"] = [];
const block = (function create_if_block(i: number): IfBlockExpression | undefined {
if (i >= segments.length) return undefined;
const _if = segments[i];
const pound = segments[i + 1];
const grp = segments[i + 2];
const block = segments[i + 3];
const _else = segments[i + 4];
assert(
isIdent(_if, "if") &&
isToken(pound, TK["#"]) &&
isGroup(grp, DelimKind["[]"]) &&
isGroup(block, DelimKind["{}"]) &&
(!_else || isIdent(_else, "else"))
);
return create_block(block, (body) =>
rs.mockNode(NodeType.IfBlockExpression, block.loc.cloneFrom(start(_if)), {
label: undefined,
condition: rs.mockNode(NodeType.Attribute, grp.loc.cloneFrom(start(pound)), {
segments: grp.segments,
value: grp.segments.loc.sliceText(),
line: false,
inner: false,
}) as any,
body: body,
else: (_else && iLast(i + 5, segments)
? function create_else_block(i: number) {
const block = segments[i];
assert(isGroup(block, DelimKind["{}"]));
return create_block(block, (body) =>
rs.mockNode(NodeType.BlockExpression, body.loc.clone(), {
label: undefined,
body,
})
);
}
: create_if_block)(i + 5),
})
);
})(0);
const ast = rs.createLocArray(
segments.dk,
segments.loc,
block && [
rs.mockNode(NodeType.ExpressionStatement, block.loc.clone(), {
expression: block,
semi: false,
}),
]
);
return rs.mockNode(NodeType.Snippet, segments.loc.clone(), { ast, danglingAttributes, comments });
function create_block<R extends NodeWithBody>(
group: DelimGroup<Segment> & { segments: { dk: 3 } },
fn: (statements: LocArray<StatementNode, "{}">) => R
): R {
const snippet = rs.toBlockBody(group.segments);
insertNodes(danglingAttributes, snippet.danglingAttributes);
insertNodes(comments, snippet.comments);
const block = fn(snippet.ast);
transferAttributes(snippet, block);
return block;
}
}

View File

@@ -0,0 +1,16 @@
import { DelimGroup, DelimKind, Identifier, LocArray, PunctuationToken, Segment, TK } from "jinx-rust";
import { isTK, is_DelimGroup, is_Identifier, is_PunctuationToken } from "jinx-rust/utils";
export function isIdent(node: Segment | undefined, name?: string): node is Identifier {
return !!node && is_Identifier(node) && (null == name || node.name === name);
}
export function isToken(node: Segment | undefined, tk?: TK): node is PunctuationToken {
return !!node && (null == tk ? is_PunctuationToken(node) : isTK(node, tk));
}
export function isGroup<D extends DelimKind>(node: Segment | undefined, dk?: D): node is DelimGroup & { segments: LocArray<any, D> } {
return !!node && is_DelimGroup(node) && (null == dk || node.segments.dk === dk);
}
export function isCallLike(tk_1: Segment | undefined, tk_2: Segment | undefined): boolean {
return !!tk_1 && !!tk_2 && is_Identifier(tk_1) && is_DelimGroup(tk_2) && tk_2.segments.dk === DelimKind["()"];
}

View File

@@ -0,0 +1,568 @@
import {
Attribute,
AttributeOrDocComment,
CallExpression,
DelimKind,
ExpressionNode,
LocArray,
MacroInvocation,
MemberExpression,
Node,
NodeType,
NodeWithBodyNoBody,
NodeWithTypeBounds,
NTMap,
ProgramLike,
rs,
Snippet,
StatementNode,
StructLiteral,
StructPattern,
TK,
TypeBound,
TypeBoundsStandaloneNode,
TypeDynBounds,
TypeTraitBound,
} from "jinx-rust";
import {
countActualNodeChildren,
deleteAttributes,
each_childNode,
end,
getActualNodeChildren,
getBodyOrCases,
getMacroName,
getNodeChildren,
hasAttributes,
hasMethod,
hasTypeBounds,
includesTK,
insertNode,
insertNodes,
is_AttributeOrDocComment,
is_BareTypeTraitBound,
is_BlockExpression,
is_CallExpression,
is_ClosureFunctionExpression,
is_DocCommentAttribute,
is_ExpressionStatement,
is_ExpressionWithBodyOrCases,
is_FlowControlExpression,
is_IfBlockExpression,
is_MacroInvocation,
is_Node,
is_NodeWithBodyNoBody,
is_NodeWithBodyOrCases,
is_Program,
is_PunctuationToken,
is_ReassignmentNode,
is_Snippet,
is_TypeBoundsStandaloneNode,
is_TypeDynBounds,
is_TypeImplBounds,
is_TypeTraitBound,
ownStart,
reassignNodeProperty,
start,
transferAttributes,
unsafe_set_nodeType,
} from "jinx-rust/utils";
import { isPrettierIgnoreAttribute, setPrettierIgnoreTarget } from "../format/comments";
import { is_StructSpread } from "../format/core";
import { CustomOptions } from "../format/external";
import { getOptions } from "../format/plugin";
import {
Array_replace,
Array_splice,
assert,
binarySearchIn,
each,
exit,
iLast,
last_of,
Map_get,
spliceAll,
try_eval,
} from "../utils/common";
import { transform_simpleAttrSyntax } from "./custom/attribute";
import { transform_macro_cfg_if } from "./custom/cfg_if";
export interface ExpressionLikeAttribute extends Attribute {
segments: LocArray<any, "[]">;
}
export interface CallLikeMacroInvocation extends MacroInvocation {
segments: LocArray<any, any>;
callee: MacroInvocation["callee"];
method: undefined;
typeArguments: undefined;
arguments: LocArray<ExpressionNode, "()" | "[]" | "{}">;
}
export interface BlockLikeMacroInvocation extends MacroInvocation {
segments: LocArray<any, any>;
body: LocArray<StatementNode, "()" | "[]" | "{}">;
attributes?: AttributeOrDocComment[];
}
export function is_CallLikeMacroInvocation(node: Node): node is CallLikeMacroInvocation {
return is_MacroInvocation(node) && "arguments" in node;
}
export function is_BlockLikeMacroInvocation(node: Node): node is BlockLikeMacroInvocation {
return is_MacroInvocation(node) && "body" in node;
}
export function is_CallExpression_or_CallLikeMacroInvocation(node: any): node is CallExpression | CallLikeMacroInvocation {
return is_CallExpression(node) || is_CallLikeMacroInvocation(node);
}
const IGNORED_MACROS = new Set([
// std
// crates
"quote",
]);
const HARDCODED_MACRO_DELIMS = new Map<string, MacroInvocation["segments"]["dk"]>();
each(
{
[DelimKind["{}"]]: [
// std
"thread_local",
// crates
"cfg_if",
],
[DelimKind["()"]]: [
// std
"assert_eq",
"assert_ne",
"assert",
"cfg",
"concat_bytes",
"concat_idents",
"concat",
"debug_assert_eq",
"debug_assert_ne",
"debug_assert",
"eprint",
"eprintln",
"format_args_nl",
"format_args",
"format",
"matches",
"panic",
"print",
"println",
"try",
"unimplemented",
"unreachable",
"write",
"writeln",
// crates
],
[DelimKind["[]"]]: [
// std
"vec",
// crates
],
},
(names, tk) =>
each(names, (name) => {
HARDCODED_MACRO_DELIMS.set(name, +tk as MacroInvocation["segments"]["dk"]);
})
);
let _COMMENTS: CustomOptions["comments"] = undefined!;
let _DANGLING_ATTRIBUTES: CustomOptions["danglingAttributes"] = undefined!;
export function transform_ast(options: CustomOptions) {
try {
_COMMENTS = options.comments;
_DANGLING_ATTRIBUTES = options.danglingAttributes;
transformNode(options.rsParsedFile);
} finally {
_depth = 0;
_COMMENTS = undefined!;
_DANGLING_ATTRIBUTES = undefined!;
}
}
let _depth = 0;
const isReadingSnippet = () => 0 !== _depth;
function maybe_transform_node<T extends Node, S extends Snippet>(
node: T,
read_snippet: () => S,
fn: (node: T, snippet: S) => void
): T | undefined {
const snippet = try_eval(read_snippet);
if (snippet) {
++_depth;
transformNode(snippet);
--_depth;
fn(node, snippet);
transformed.add(node);
return node;
}
}
const transformed = new WeakSet<Node>();
export function isTransformed(node: Node) {
return transformed.has(node);
}
const transform: { [K in NodeType]?: (node: NTMap[K]) => void } = {
[NodeType.Attribute](node) {
try_eval(() => {
node.segments = rs.createLocArray(node.segments.dk, node.segments.loc.clone(), [
transform_simpleAttrSyntax(node.segments),
]) as any;
transformed.add(node);
});
},
[NodeType.MacroInlineRuleDeclaration](node) {
node.match.dk = DelimKind["()"];
node.transform.dk = DelimKind["{}"];
},
[NodeType.MacroInvocation](node) {
const name = getMacroName(node);
if (
IGNORED_MACROS.has(name) ||
node.segments.length === 0 ||
(node.segments.length === 1 && is_PunctuationToken(node.segments[0]))
) {
return;
}
const tk = transformMacroDelim(name, node);
if (name === "matches") {
//
}
if (name === "if_chain") {
//
}
if (name === "cfg_if") {
transformBlockLike(() => transform_macro_cfg_if(node.segments) as any);
} else if (tk === DelimKind["{}"]) {
transformBlockLike(); /* || (includesTK(node, TK[","]) && transformCallLike()); */
} else {
transformCallLike(); /* || (includesTK(node, TK[";"]) && transformBlockLike()); */
}
function transformBlockLike(transform = () => rs.toBlockBody(node.segments)) {
return maybe_transform_node(node as BlockLikeMacroInvocation, transform, (node, snippet) => {
const _body = snippet.ast;
_body.dk = tk;
node.body = _body;
node.segments = _body;
transferAttributes(snippet, node);
});
}
function transformCallLike() {
return maybe_transform_node(
node as CallLikeMacroInvocation,
() => rs.toCallExpressionArguments(node.segments),
(node, snippet) => {
const _arguments = snippet.ast;
_arguments.dk = tk;
node.method = undefined;
node.typeArguments = undefined;
node.arguments = _arguments;
node.segments = _arguments;
}
);
}
},
[NodeType.CallExpression](node) {
if (hasMethod(node)) {
node.callee = rs.mockNode(NodeType.MemberExpression, node.method.loc.cloneFrom(start(node.callee)), {
expression: node.callee,
property: node.method,
computed: false,
});
node.method = undefined!;
getOptions().actuallyMethodNodes.add(node.callee as MemberExpression);
}
},
[NodeType.AutoTraitDeclaration](node) {
mockBodyNoBody(node);
},
[NodeType.NegativeImplDeclaration](node) {
mockBodyNoBody(node);
},
[NodeType.StructLiteral](node) {
moveSpreadsToEnd(node);
},
[NodeType.StructPattern](node) {
moveSpreadsToEnd(node);
},
};
function moveSpreadsToEnd(node: StructLiteral | StructPattern) {
const props = node.properties;
if (props.some((p, i, a) => is_StructSpread(p) && !iLast(i, a))) {
const spreads: any[] = [];
for (let i = 0; i < props.length; i++) {
const prop = props[i];
if (is_StructSpread(prop)) {
Array_splice(props, prop, i--);
spreads.push(prop);
}
}
props.push(...spreads);
}
}
function mockBodyNoBody(node: NodeWithBodyNoBody) {
// @ts-expect-error
node.body = rs.createLocArray(last_of(rs.toTokens(node).ast).loc.clone(), DelimKind["{}"]);
}
function transformMacroDelim(name: string, node: MacroInvocation): 1 | 2 | 3 {
if (HARDCODED_MACRO_DELIMS.has(name)) {
return HARDCODED_MACRO_DELIMS.get(name)!;
}
if (node.segments.dk === DelimKind["{}"] && includesTK(node, TK[","])) {
return DelimKind["()"];
}
if (node.segments.dk === DelimKind["()"] && includesTK(node, TK[";"])) {
return DelimKind["{}"];
}
return node.segments.dk;
}
// export function createTransformed<S extends Node>(create: () => S): S {
// return transformNode(create());
// }
const seen = new WeakSet<Node>();
function transformNode<T extends Node>(node: T, parent?: Node, key?: string, index?: any): T {
if (!seen.has(node)) {
seen.add(node);
if (is_Snippet(node) || is_Program(node)) {
registerPogramLike(node);
}
each_childNode(node, transformNode);
insert_blocks(node, parent, key, index);
transform[node.nodeType]?.(node as any);
flatten_typeBounds(node);
transform_nodeAttributes(node);
}
return node;
}
function insert_blocks(node: Node, parent?: Node, key?: string, index?: any) {
if (parent && key) {
if (
!is_ExpressionStatement(parent) &&
(false ||
// "1 + break" -> "1 + { break; }"
is_FlowControlExpression(node) ||
// "1 + a = b" -> "1 + { a = b; }"
(!isReadingSnippet() && is_ReassignmentNode(node) && !(is_ReassignmentNode(parent) && parent.left === node)))
) {
reassignNodeProperty(blockify(node), parent, key, index);
} else if (
is_ClosureFunctionExpression(node) &&
(false ||
// "|| -> T x" -> "|| -> T { x }"
(!!node.returnType && !is_BlockExpression(node.expression)) ||
// "|| match x {}" -> "|| { match x {} }"
(is_ExpressionWithBodyOrCases(node.expression) &&
!is_BlockExpression(node.expression) &&
!is_IfBlockExpression(node.expression)))
) {
node.expression = blockify(node.expression);
}
}
function blockify(node: ExpressionNode) {
const block = rs.mockNode(NodeType.BlockExpression, node.loc.clone(), {
label: undefined,
body: rs.createLocArray(DelimKind["{}"], node.loc.clone(), [
rs.mockNode(NodeType.ExpressionStatement, node.loc.clone(), { semi: false, expression: node }),
]),
});
transferAttributes(node, block);
return block;
}
}
function flatten_typeBounds(topNode: Node) {
if (hasTypeBounds(topNode)) {
const nestedBounds: TypeTraitBound[] = topNode.typeBounds.filter(isBoundWithNestedBounds);
const [first, ...subsequent] = nestedBounds;
const flatten = (bound: TypeTraitBound) =>
Array_replace(topNode.typeBounds, bound, ...(bound.typeExpression as unknown as TypeDynBounds).typeBounds);
if (nestedBounds.every(isBareBoundWithNestedBoundsNoPrefix)) {
// A + (B + C)
// -> A + B + C
each(nestedBounds, flatten);
} else if (
!hasDefinedPrefix(topNode) &&
first === topNode.typeBounds[0] &&
!isBareBoundWithNestedBoundsNoPrefix(first) &&
subsequent.every(isBareBoundWithNestedBoundsNoPrefix)
) {
if (is_TypeDynBounds(topNode)) {
if (is_TypeImplBounds(first.typeExpression)) {
// (impl A) + B
// -> impl A + B
unsafe_set_nodeType(topNode, NodeType.TypeImplBounds);
} else {
// (dyn A) + B
// -> dyn A + B
topNode.dyn = true;
}
each(nestedBounds, flatten);
} else {
each(subsequent, flatten);
(first.typeExpression as unknown as TypeDynBounds).typeBounds.push(...topNode.typeBounds.slice(1));
topNode.typeBounds.length = 1;
}
}
}
function isBoundWithNestedBounds(bound: TypeBound): bound is TypeTraitBound & { typeExpression: TypeBoundsStandaloneNode } {
return is_TypeTraitBound(bound) && is_TypeBoundsStandaloneNode(bound.typeExpression);
}
function isBareBoundWithNestedBounds(bound: TypeBound): bound is TypeTraitBound & { typeExpression: TypeBoundsStandaloneNode } {
return isBoundWithNestedBounds(bound) && is_BareTypeTraitBound(bound);
}
function isBareBoundWithNestedBoundsNoPrefix(bound: TypeBound): bound is TypeTraitBound & { typeExpression: TypeDynBounds } {
return isBareBoundWithNestedBounds(bound) && !hasDefinedPrefix(bound.typeExpression);
}
function hasDefinedPrefix(node: NodeWithTypeBounds) {
return (is_TypeDynBounds(node) && node.dyn) || is_TypeImplBounds(node);
}
}
function transform_nodeAttributes(node: Node) {
/**
* # Inside Token trees:
*
* 1. DocCommentAttribute --is parsed as--> Comment
* 2. Attribute --is parsed as--> Token<'#'>, DelimGroup<'[]'>
*
* # Transforming tokens into a Snippet:
*
* 1. DocCommentAttribute <--replace from-- Comment
* a) Remove node with same loc from comments
* b) Merge Snippet.danglingAttributes with Program.danglingAttributes
*
* 2. Attribute (no action needed)
*
*/
if (hasAttributes(node)) {
const attrs = node.attributes;
for (let i = 0; i < attrs.length; i++) {
const attr = attrs[i];
if (isReadingSnippet() && is_DocCommentAttribute(attr)) {
const index = binarySearchIn(_COMMENTS, start(attr), start);
__DEV__: assert(index !== -1), assert(end(_COMMENTS[index]) === end(attr));
_COMMENTS.splice(index, 1);
}
if (attr.inner) {
if (isPrettierIgnoreAttribute(attr)) {
setPrettierIgnoreTarget(is_Program(node) ? node.loc.src : node, attr);
}
// @ts-expect-error Inserting Attribute into StatementNode[]
insertNode(is_Snippet(node) ? node.ast : getBodyOrCases(node)!, attr);
Array_splice(attrs, attr, i--);
}
}
if (attrs.length === 0) {
deleteAttributes(node);
}
}
}
function registerPogramLike(program: Extract<Node, ProgramLike>) {
const comments = spliceAll(program.comments);
const danglingAttributes = spliceAll(program.danglingAttributes);
for (let i = 0; i < danglingAttributes.length; i++) {
const attr = danglingAttributes[i];
// if (isReadingSnippet() && is_DocCommentAttribute(attr)) {
// }
if (is_DocCommentAttribute(attr)) {
if (isReadingSnippet()) {
const index = binarySearchIn(_COMMENTS, start(attr), start);
__DEV__: assert(index !== -1), assert(end(_COMMENTS[index]) === end(attr));
_COMMENTS.splice(index, 1);
}
} else {
transformNode(danglingAttributes[i], program, "danglingAttributes", i);
}
}
if (!isReadingSnippet()) insertNodes(_COMMENTS, comments);
insertNodes(_DANGLING_ATTRIBUTES, danglingAttributes);
}
const CommentChildNodes = new WeakMap<Node, Node[]>();
export function getCommentChildNodes(n: any): Node[] {
const children = Map_get(CommentChildNodes, n, getTransformedNodeChildren);
/**
* parent {
* #[attr]
* #![attr] <-------- list misplaced inner attrs as part of "#[attr] child {}"
* child {}
* }
*/
if (is_NodeWithBodyOrCases(n) || is_BlockLikeMacroInvocation(n)) {
for (let i = 0; i < children.length; i++) {
const attr = children[i];
if (is_AttributeOrDocComment(attr)) {
const target = children.find((n) => start(n) <= start(attr) && ownStart(n) >= end(attr));
if (target) {
children.splice(i--, 1);
insertNode(Map_get(CommentChildNodes, target, getTransformedNodeChildren), attr);
}
}
}
}
return children;
function getTransformedNodeChildren(node: Node) {
if (is_Program(node)) node.comments ??= []; // prettier core deletes this property
const children = getNodeChildren(node);
if (is_NodeWithBodyNoBody(node)) {
insertNodes(children, (node as any).body);
}
__DEV__: {
const actual_count = countActualNodeChildren(node);
if (
children.length !== actual_count &&
!(is_MacroInvocation(node) && actual_count - node.segments.length === children.length)
) {
const actual = getActualNodeChildren(node);
const missing = actual.filter((n) => !children.includes(n));
const unknown = children.filter((n) => !actual.includes(n));
const duplicates_in_object = actual.filter((n, i, a) => i !== 0 && n === a[i - 1]);
const duplicates_in_childNodes = children.filter((n, i, a) => i !== 0 && n === a[i - 1]);
const ctx = { missing, unknown, duplicates_in_object, duplicates_in_childNodes };
for (let key in ctx) if (ctx[key].length === 0) delete ctx[key];
exit(`${node.type} was transformed but did not patch its childNodes list`, ctx, node.loc.url(), node);
}
for (const child of children)
if (!is_Node(child)) exit(`${node.type}'s childNodes includes unexpected entries`, { node, child });
}
return children;
}
}

View File

@@ -0,0 +1,246 @@
import { createCustomError } from "./debug";
declare global {
interface ImportMeta {
url: string;
}
}
export function Narrow<T extends R, R = unknown>(value: R): asserts value is T {}
export function AssertTypesEq<A extends B, B>(...args: [B] extends [A] ? [] : [RIGHT_TYPES_NOT_ASSIGNABLE_TO_LEFT: Exclude<B, A>]) {}
// prettier-ignore
type indexof<A> = A extends readonly any[] ? A extends 0 ? any : keyof A & number : A extends Set<unknown> ? never : A extends Map<infer U, unknown> ? U
: A extends Iterable<unknown> ? never : A extends object ? keyof A & (number | string) : never;
// prettier-ignore
type valueof<A> = A extends ReadonlyArray<infer U> ? A extends 0 ? any : U : A extends Set<infer U> ? U : A extends Map<unknown, infer U> ? U
: A extends Iterable<infer U> ? U : A extends object ? A[keyof A & (number | string)] : never;
// prettier-ignore
type vObject<V extends unknown = unknown, K extends unknown = unknown> = | object | readonly V[] | { [key: string]: V } | anySet<V> | anyMap<K, V>;
export type itfn<A, R> = (value: valueof<A>, key: indexof<A>) => R;
type anySet<V extends unknown = unknown> = Set<V>;
type anyMap<K extends unknown = unknown, V extends unknown = unknown> = Map<K, V>;
type anyfunction<A extends any[] = unknown[], R = unknown> = (...args: A) => R;
type objlike = object | anyfunction;
type anymap<K extends unknown = unknown, V extends unknown = unknown> = K extends objlike ? Map<K, V> | WeakMap<K, V> : Map<K, V>;
export function exit(message: string, ...ctx: any[]): never {
if (ctx.length > 0) console.log("Error context:", { ...ctx });
throw createCustomError({ message });
}
exit.never = function never(...ctx: any[]): never {
exit("Reached unreachable code", ...ctx);
};
export function assert(predicate: boolean, err?: string, ...ctx: any[]): asserts predicate {
__DEV__: if (typeof predicate !== "boolean") exit("Expected boolean", predicate);
if (false === predicate) exit(err ?? "Assertion failed", ...ctx);
}
export function Identity<T>(v: T): T {
return v;
}
export function last_of<T extends ArrayLike<any>>(arr: T): T extends readonly [...infer A, infer U] ? U : T[number] {
__DEV__: isArrayLike(arr) || exit("Expected Array"), arr.length > 0 || exit("Attempted to retrieve last item of an empty array", arr);
return arr[arr.length - 1];
}
export function maybe_last_of<T extends readonly any[] | undefined>(
arr: T
): T extends any[] ? (T extends readonly [...infer A, infer U] ? U : T[number]) : undefined {
return (undefined === arr || 0 === arr.length ? undefined : last_of(arr as any[])) as any;
}
export function normPath(filepath: string) {
return filepath.replace(/^file:\/\/\//, "").replace(/\\\\?/g, "/");
}
export function print_string(str: string) {
return /[\u0000-\u0020]/.test(str)
? str
.replace(/ /g, "•")
.replace(/\n/g, "↲")
.replace(/\t/g, "╚")
.replace(/[\u0000-\u0020]/g, "□")
: str;
}
function isArrayLike(value: any): value is ArrayLike<unknown> {
return is_object(value) && oisArrayLike(value);
}
function oisArrayLike(value: {}): value is ArrayLike<unknown> {
return "length" in value && (0 === (value as any).length || "0" in value);
}
export function binarySearchIn<T extends {}>(array: ArrayLike<T>, target: number, toValue: (item: T) => number) {
if (isEmpty(array)) return -1;
let i = 0;
let low = 0;
let high = array.length - 1;
let value = toValue(array[high]);
if (target >= value) return high;
else high--;
while (low <= high) {
i = low + ((high - low) >> 1);
value = toValue(array[i]);
if (target === value) return i;
if (target > value) low = i + 1;
else high = i - 1;
}
return low - 1;
}
export function getTerminalWidth(fallbackWidth = 200) {
return globalThis?.process?.stdout?.columns ?? fallbackWidth;
}
// @ts-ignore
const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
export const color = ((cfn, mfn) => ({
black: cfn(30),
red: cfn(31),
green: cfn(32),
yellow: cfn(33),
blue: cfn(34),
magenta: cfn(35),
cyan: cfn(36),
white: cfn(37),
grey: cfn(90),
bold: mfn(1, 22),
italic: mfn(3, 23),
underline: mfn(4, 24),
hidden: mfn(8, 28),
hiddenCursor: (str: string) => `\x1B[?25l${str}\x1B[?25h`,
unstyle: (str: string) => str.replace(/\x1B\[[0-9][0-9]?m/g, ""),
unstyledLength: (str: string) => str.replace(/\x1B\[[0-9][0-9]?m/g, "").length,
link: (str: string) => color.underline(color.blue(str)),
}))(
(c1: number) => (isBrowser ? Identity : (str: string) => `\x1B[${c1}m${str.replace(/\x1B\[39m/g, `\x1B[${c1}m`)}\x1B[39m`),
(c1: number, c2: number) => (isBrowser ? Identity : (str: string) => `\x1B[${c1}m${str}\x1B[${c2}m`)
);
export function Map_get<K extends object, V>(map: WeakMap<K, V>, key: K, init: (key: K) => V): V;
export function Map_get<K, V>(map: Map<K, V>, key: K, init: (key: K) => V): V;
export function Map_get<K, V>(map: anymap<K, V>, key: K, init: (key: K) => V): V {
if (!map.has(key)) map.set(key, init(key));
return map.get(key)!;
}
export function isEmpty(array: ArrayLike<any>): boolean {
__DEV__: assert(isArrayLike(array));
return 0 === array.length;
}
export function Array_splice<T extends any[]>(array: T, target: T[number], index: number = array.indexOf(target)) {
__DEV__: {
const i = arguments.length === 2 ? array.indexOf(target) : index;
assert(i === index && i !== -1 && i === array.lastIndexOf(target), "", { array, target, index, i });
}
array.splice(index, 1);
}
export function Array_replace<T extends any[]>(array: T, target: T[number], ...replacements: T[number][]) {
const i = array.indexOf(target);
__DEV__: if (i === -1 || i !== array.lastIndexOf(target))
exit("Array_replace", { index: i, lastIndex: array.lastIndexOf(target), array, target, replacements });
array.splice(array.indexOf(target), 1, ...replacements);
}
export function has_key_defined<T extends object, K extends T extends never ? never : keyof T>(
o: T,
k: K
): o is K extends never
? never
: T extends { [k in K]: any }
? T & { [k in K]: {} }
: T extends { [k in K]?: any }
? T & { [k in K]: {} }
: never {
return k in o && undefined !== o[k];
}
export function is_object(data: unknown): data is object | ({ [key: string]: unknown } | unknown[]) {
return "object" === typeof data && null !== data;
}
export function is_array(data: unknown): data is any[] {
return Array.isArray(data);
}
function ois_vobject(data: any) {
__DEV__: assert(is_object(data));
switch (data.constructor) {
case Array:
case Object:
case Set:
case Map:
return true;
default:
return false;
}
}
export function each<A extends vObject>(data: A, callback: itfn<A, void>): void;
export function each(data: any, callback: (value: any, index: any) => void): void {
__DEV__: assert(ois_vobject(data));
// prettier-ignore
switch (data.constructor) {
case Array: { let i = 0; for (; i < data.length; i++) callback(data[i], i); return; }
case Object: { let k; for (k in data) callback(data[k], k); return; }
case Set: { let d; for (d of data) callback(d, undefined!); return; }
case Map: { let e; for (e of data) callback(e[1], e[0]); return; }
default: { let x; for (x of data) callback(x, undefined!); return; }
}
}
export function iLast(index: number, array: any[]) {
return 1 + index === array.length;
}
export function find_last<T>(arr: T[], test: itfn<T[], boolean>): T | undefined {
for (var i = arr.length; --i !== -1; ) if (test(arr[i], i)) return arr[i];
}
export function try_eval<T>(fn: () => T): T | undefined {
try {
return fn();
} catch (e) {
return undefined;
}
}
export function clamp(min: number, max: number, value: number) {
return value > min ? (value < max ? value : max) : min;
}
export type MaybeFlatten<T> = T extends ReadonlyArray<infer U> ? MaybeFlatten<Exclude<U, T>> : T;
export type FlatArray<T> = MaybeFlatten<T>[];
export function flat<T extends readonly any[]>(arr: T): FlatArray<T> {
return (arr as any as [any]).flat(Infinity);
}
export function flatMap<T extends readonly any[], R>(arr: T, mapFn: (item: T[number], index: number, array: T) => R): FlatArray<R> {
return flat(arr.map(mapFn as any));
}
export function joinln(...arr: string[]): string {
return arr.join("\n");
}
export function join_lines(fn: () => Generator<string, void, void>): string {
return [...fn()].join("\n");
}
export function reduce_tagged_template<T>(args: [strings: TemplateStringsArray, ...values: T[]], map: (value: T) => string) {
for (var str = "" + args[0][0], i = 1; i < args.length; i++) str += map(args[i] as T) + args[0][i];
return str;
}
export function map_tagged_template<T, R>(args: [strings: TemplateStringsArray, ...values: T[]], map: (value: T) => R) {
const arr: (R | string)[] = [args[0][0]];
for (var i = 1; i < args.length; i++) arr.push(map(args[i] as T), args[0][i]);
return arr;
}
export function spliceAll<T extends any[]>(array: T): [...T] {
const r: [...T] = [...array];
array.length = 0;
return r;
}
export function spread<R>(fn: () => Iterable<R>): R[] {
return [...fn()];
}

View File

@@ -0,0 +1,141 @@
import { clamp, color, getTerminalWidth, normPath } from "./common";
const cwd =
typeof process === "object" && typeof process?.cwd === "function" ? /* @__PURE__ */ normPath(/* @__PURE__ */ process.cwd() ?? "") : "";
function normPath_strip_cwd(filepath: string) {
let normFilePath = normPath(filepath);
return normFilePath.startsWith(cwd) ? normFilePath.slice(cwd.length + 1) : normFilePath;
}
type StackStyleFn = (callee: string, item: StackItem) => (str: string) => string;
interface Stack extends Array<StackItem> {
message: string;
style?: { callee?: StackStyleFn; url?: StackStyleFn } | undefined;
}
class StackLine {
declare readonly raw: string;
declare readonly callee: string;
declare readonly filepath: string;
declare readonly line: string;
declare readonly col: string;
declare readonly other: string;
declare readonly url: string;
constructor(raw: string) {
({
1: this.callee = "",
2: this.filepath = "",
3: this.line = "",
4: this.col = "",
5: this.other = "",
} = (this.raw = raw).match(/at (?:(.+?)\s+\()?(?:(.+?):([0-9]+)(?::([0-9]+))?|([^)]+))\)?/) ?? ["", "", "", "", "", ""]);
this.url = this.filepath //
? normPath_strip_cwd(this.filepath) + (this.line && this.col && `:${this.line}:${this.col}`)
: this.other === "native"
? "<native>"
: "";
}
}
function getPrintWidth() {
return clamp(0, getTerminalWidth(128), 200) - 4;
}
class StackItem extends StackLine {
constructor(private readonly stack: Stack, readonly i: number, raw: string) {
super(raw);
}
hidden = false;
hide() {
this.hidden = true;
return this;
}
hideNext(n: number) {
for (let i = 0; i < n; i++) this.at(i)?.hide();
}
hideWhileTrue(test: (line: StackItem) => boolean) {
let line: StackItem | undefined = this;
while (line && test(line)) line = line.hide().next();
}
at(relIndex: number) {
return this.i + relIndex >= this.stack.length || this.i + relIndex < 0 ? undefined : this.stack[this.i + relIndex];
}
next() {
return this.at(+1);
}
toString() {
const url = this.url;
const calleeColor = this.stack.style?.callee?.(this.callee, this) ?? color.cyan;
const urlColor = this.stack.style?.url?.(url, this) ?? color.grey;
return compose2Cols(" at " + calleeColor(this.callee), urlColor(url), getPrintWidth());
}
}
// prettier-ignore
function createStack(message: string, Error_stack: string, style: Stack["style"]): Stack {
for (var STACK: Stack = [] as any, i = 0, stack = Error_stack.split("\n").slice(2); i < stack.length; i++) STACK[i] = new StackItem(STACK, i, stack[i]);
return (STACK.message = message), (STACK.style = style), STACK;
}
function composeStack(stack: Stack) {
var hidden = 0;
var str = stack.message;
for (var item of stack) item.hidden ? ++hidden : (str += "\n" + item.toString());
return str + (hidden > 0 ? "\n" + color.grey(compose2Cols("", `...filtered ${hidden} lines`, getPrintWidth())) : "");
}
export function get_caller_cmd(offset = 0) {
const obj: { stack: string } = {} as any;
Error.captureStackTrace(obj, get_caller_cmd);
const lines = obj.stack.split("\n");
return new StackLine(lines[1 + clamp(0, lines.length - 2, offset)]).url;
}
var Error_prepareStackTrace;
let replaced_default_prepareStackTrace = false;
function custom_prepareStackTrace(err, calls) {
return (Error_prepareStackTrace?.(err, calls) ?? calls.join("\n"))?.replace(/file:\/\/\//g, "").replace(/\\\\?/g, "/") ?? calls;
}
export function overrideDefaultError(silent = false) {
if (replaced_default_prepareStackTrace === (replaced_default_prepareStackTrace = true)) return;
Error_prepareStackTrace = Error.prepareStackTrace ?? ((_, calls) => calls.join("\n"));
Error.prepareStackTrace = custom_prepareStackTrace;
if (!silent) console.log(color.grey(`[devtools] Replaced Error.prepareStackTrace at ${get_caller_cmd(1)}`));
}
export function createCustomError({
message = "Unknown Error",
editStack = (stack: StackItem[]) => {},
style = undefined as Stack["style"],
stackTraceLimit = 20,
}): Error {
const _stackTraceLimit = Error.stackTraceLimit;
const _prepareStackTrace = Error.prepareStackTrace;
if (replaced_default_prepareStackTrace && _prepareStackTrace === custom_prepareStackTrace)
Error.prepareStackTrace = Error_prepareStackTrace;
Error.stackTraceLimit = stackTraceLimit;
const _ctx: { stack: string } = {} as any;
Error.captureStackTrace(_ctx, createCustomError);
const stack = createStack(message, _ctx.stack, style);
Error.prepareStackTrace = function (err, calls) {
editStack(stack);
return composeStack(stack);
};
const err = new Error(message); // (get) to trigger prepareStackTrace, (set) to prevent treeshaking
err.stack = err.stack;
Error.stackTraceLimit = _stackTraceLimit;
Error.prepareStackTrace = _prepareStackTrace;
return err;
}
function compose2Cols(left: string, right: string, len = 64, min = 1) {
return left + " ".repeat(clamp(min, len, len - (color.unstyledLength(left) + color.unstyledLength(right)))) + right;
}

View File

@@ -0,0 +1,109 @@
import { createScalaPrinter } from "./printer";
import { parse, type ScalaCstNode, type IToken } from "./scala-parser";
import { type Plugin, type SupportOption } from "prettier";
/**
* Prettierがサポートする言語の定義
*/
const languages = [
{
name: "Scala",
parsers: ["scala"],
extensions: [".scala", ".sc"],
vscodeLanguageIds: ["scala"],
},
];
/**
* Scalaパーサーの定義
*/
const parsers = {
scala: {
parse: (text: string) => {
const result = parse(text);
// シンプルなコメント保持: ASTに格納してvisitorで処理
const ast = {
...result.cst,
comments: [], // Prettierの検証を回避
originalComments: result.comments || [], // プラグイン独自のコメント格納
type: "compilationUnit",
};
return ast;
},
astFormat: "scala-cst",
locStart: (node: ScalaCstNode | IToken) => {
// Handle comment tokens (from Chevrotain lexer)
if ("startOffset" in node && node.startOffset !== undefined) {
return node.startOffset;
}
// Handle CST nodes
if ("location" in node && node.location?.startOffset !== undefined) {
return node.location.startOffset;
}
return 0;
},
locEnd: (node: ScalaCstNode | IToken) => {
// Handle comment tokens (from Chevrotain lexer)
if ("endOffset" in node && node.endOffset !== undefined) {
return node.endOffset + 1; // Chevrotain endOffset is inclusive, Prettier expects exclusive
}
// Handle CST nodes
if ("location" in node && node.location?.endOffset !== undefined) {
return node.location.endOffset + 1; // Chevrotain endOffset is inclusive, Prettier expects exclusive
}
return 1;
},
hasPragma: () => false,
},
};
/**
* プリンターの定義
*/
const printers = {
"scala-cst": createScalaPrinter(),
};
/**
* プラグインオプションscalafmt互換性 - フェーズ1
*/
const options: Record<string, SupportOption> = {
// Prettier standard options with Scala-specific defaults
semi: {
type: "boolean",
default: false, // Scala convention: omit semicolons
description: "Add semicolons at the end of statements",
category: "Global",
} as const,
// Deprecated options (backward compatibility)
scalaLineWidth: {
type: "int",
default: 80,
description: "Maximum line width (DEPRECATED: use printWidth instead)",
category: "Scala",
} as const,
scalaIndentStyle: {
type: "choice",
default: "spaces",
choices: [
{ value: "spaces", description: "Use spaces for indentation" },
{ value: "tabs", description: "Use tabs for indentation" },
],
description: "Indentation style (DEPRECATED: use useTabs instead)",
category: "Scala",
} as const,
};
/**
* Prettierプラグインのエクスポート
*/
const plugin: Plugin = {
languages,
parsers,
printers,
options,
};
export default plugin;

View File

@@ -0,0 +1,91 @@
import { CstNodeVisitor, type CSTNode } from "./visitor";
import type { ScalaCstNode, IToken } from "./scala-parser";
import { type Doc, type Printer, type AstPath, type Options } from "prettier";
/**
* Scala用のPrettierプリンターを作成
* @returns Prettierプリンターオブジェクト
*/
export function createScalaPrinter(): Printer {
return {
/**
* ASTードをフォーマット済みのテキストに変換
* @param path - 現在のノードへのパス
* @param options - Prettierオプション
* @param print - 子ノードを印刷するための関数
* @returns フォーマット済みのDoc
*/
print(
path: AstPath<ScalaCstNode>,
options: Options,
print: (path: AstPath) => Doc,
): Doc {
const node = path.getValue();
const visitor = new CstNodeVisitor();
const result = visitor.visit(node, {
path,
options: {
printWidth: options.printWidth,
tabWidth: options.tabWidth,
useTabs: options.useTabs,
semi: options.semi,
singleQuote: options.singleQuote,
trailingComma:
options.trailingComma === "es5" ? "all" : options.trailingComma,
},
print: (childNode: CSTNode) => {
// 子ノード用のモックパスを作成
const mockPath = {
getValue: () => childNode,
call: (fn: () => unknown) => fn(),
};
return String(print(mockPath as AstPath<unknown>));
},
indentLevel: 0,
});
// 文字列結果をPrettierのDocに変換
return result;
},
/**
* コメントを印刷
* @param path - コメントトークンへのパス
* @returns フォーマット済みのコメント
*/
printComment(path: AstPath<IToken>): Doc {
const comment = path.getValue();
if (!comment) return "";
// Chevrotainのimageプロパティを使用
if (typeof comment.image === "string") {
return comment.image;
}
// fallback
if (typeof comment.image === "string") {
return comment.image;
}
// デバッグ: コメント構造を確認
console.log("Unexpected comment structure in printComment:", comment);
return "";
},
canAttachComment(): boolean {
// コメント機能を一時的に無効化
return false;
},
willPrintOwnComments(): boolean {
return false; // Prettier標準のコメント処理を使用しない
},
insertPragma(text: string): string {
return text;
},
hasPrettierIgnore(): boolean {
return false;
},
isBlockComment(comment: IToken): boolean {
return comment.tokenType?.name === "BlockComment";
},
};
}

View File

@@ -0,0 +1,206 @@
import { ScalaLexer } from "./lexer";
import { parserInstance } from "./parser";
import type {
ParseResult,
ScalaCstNode,
TokenBounds,
LineColumn,
} from "./types";
import type { IToken, CstElement } from "chevrotain";
export { ScalaLexer, allTokens } from "./lexer";
export { ScalaParser, parserInstance } from "./parser";
export type {
ParseResult,
ScalaCstNode,
TokenBounds,
LineColumn,
} from "./types";
export type { IToken } from "chevrotain";
/**
* CSTードに位置情報を自動設定するヘルパー関数
* @param cst - 処理対象のCSTード
* @param tokens - 解析で使用されたトークンの配列
* @param text - 元のソースコードテキスト
* @returns 位置情報が付与されたCSTード
*/
function addLocationToCST(
cst: ScalaCstNode,
tokens: IToken[],
text: string,
): ScalaCstNode {
if (!cst || !tokens) return cst;
// テキストから行の開始位置を計算
const lineStarts = [0]; // 最初の行は0から始まる
for (let i = 0; i < text.length; i++) {
if (text[i] === "\n") {
lineStarts.push(i + 1);
}
}
// オフセットから行番号と列番号を取得
function getLineAndColumn(offset: number): LineColumn {
let line = 1;
for (let i = 0; i < lineStarts.length - 1; i++) {
if (offset >= lineStarts[i] && offset < lineStarts[i + 1]) {
line = i + 1;
break;
}
}
if (offset >= lineStarts[lineStarts.length - 1]) {
line = lineStarts.length;
}
const column = offset - lineStarts[line - 1] + 1;
return { line, column };
}
// トークンから最小・最大位置を計算
function findTokenBounds(node: ScalaCstNode): TokenBounds | null {
if (!node) return null;
let minStart = Infinity;
let maxEnd = -1;
function findTokensInNode(n: ScalaCstNode | IToken): void {
if (!n) return;
// トークンの場合
if (
"startOffset" in n &&
"endOffset" in n &&
n.startOffset !== undefined &&
n.endOffset !== undefined
) {
minStart = Math.min(minStart, n.startOffset);
maxEnd = Math.max(maxEnd, n.endOffset);
return;
}
// CSTードの場合
if ("children" in n && n.children) {
for (const children of Object.values(n.children)) {
if (Array.isArray(children)) {
children.forEach((child) => {
// CstElementをScalaCstNode | ITokenに安全に変換
if ("children" in child) {
findTokensInNode(child as ScalaCstNode);
} else {
findTokensInNode(child as IToken);
}
});
}
}
}
}
findTokensInNode(node);
if (minStart === Infinity || maxEnd === -1) {
return null;
}
return { start: minStart, end: maxEnd };
}
// 再帰的にCSTードに位置情報を設定
function setCSTLocation(node: ScalaCstNode): ScalaCstNode {
if (!node) return node;
// トークンの場合はそのまま返す
if (node.startOffset !== undefined) {
return node;
}
// CSTードの場合
if (node.children) {
// 子ノードを先に処理
const processedChildren: Record<string, CstElement[]> = {};
for (const [key, children] of Object.entries(node.children)) {
if (Array.isArray(children)) {
processedChildren[key] = children.map((child) => {
if ("children" in child) {
return setCSTLocation(child as ScalaCstNode);
}
return child; // IToken
});
}
}
// このノードの位置を計算
const bounds = findTokenBounds({ ...node, children: processedChildren });
if (bounds) {
const startLoc = getLineAndColumn(bounds.start);
const endLoc = getLineAndColumn(bounds.end);
return {
...node,
children: processedChildren,
location: {
startOffset: bounds.start,
endOffset: bounds.end,
startLine: startLoc.line,
endLine: endLoc.line,
startColumn: startLoc.column,
endColumn: endLoc.column,
},
};
} else {
return {
...node,
children: processedChildren,
};
}
}
return node;
}
return setCSTLocation(cst);
}
export function parse(text: string): ParseResult {
// Use legacy parser for now until modular parser is fixed
return parseLegacy(text);
}
// Legacy parser function (has left recursion issues)
export function parseLegacy(text: string): ParseResult {
// Tokenize
const lexResult = ScalaLexer.tokenize(text);
if (lexResult.errors.length > 0) {
throw new Error(
`Lexing errors: ${lexResult.errors.map((e) => e.message).join(", ")}`,
);
}
// Parse
parserInstance.input = lexResult.tokens;
const cst = parserInstance.compilationUnit();
if (parserInstance.errors.length > 0) {
throw new Error(
`Parsing errors: ${parserInstance.errors.map((e) => e.message).join(", ")}`,
);
}
// CSTに位置情報を追加
const cstWithLocation = addLocationToCST(
cst as ScalaCstNode,
lexResult.tokens,
text,
);
return {
cst: cstWithLocation,
errors: [],
comments: lexResult.groups.comments || [],
};
}
// Note: parseModular function was removed as the modular parser integration
// is still in development. Use the main parse() function instead.

View File

@@ -0,0 +1,479 @@
import { createToken, Lexer, ILexingResult } from "chevrotain";
// Keywords
export const Val = createToken({ name: "Val", pattern: /val\b/ });
export const Var = createToken({ name: "Var", pattern: /var\b/ });
export const Def = createToken({ name: "Def", pattern: /def\b/ });
export const Class = createToken({ name: "Class", pattern: /class\b/ });
export const ObjectKeyword = createToken({
name: "Object",
pattern: /object\b/,
});
export const Trait = createToken({ name: "Trait", pattern: /trait\b/ });
export const Extends = createToken({ name: "Extends", pattern: /extends\b/ });
export const With = createToken({ name: "With", pattern: /with\b/ });
export const If = createToken({ name: "If", pattern: /if\b/ });
export const Else = createToken({ name: "Else", pattern: /else\b/ });
export const While = createToken({ name: "While", pattern: /while\b/ });
export const For = createToken({ name: "For", pattern: /for\b/ });
export const Yield = createToken({ name: "Yield", pattern: /yield\b/ });
export const Return = createToken({ name: "Return", pattern: /return\b/ });
export const New = createToken({ name: "New", pattern: /new\b/ });
export const This = createToken({ name: "This", pattern: /this\b/ });
export const Super = createToken({ name: "Super", pattern: /super\b/ });
export const Package = createToken({ name: "Package", pattern: /package\b/ });
export const Import = createToken({ name: "Import", pattern: /import\b/ });
export const Case = createToken({ name: "Case", pattern: /case\b/ });
export const Match = createToken({ name: "Match", pattern: /match\b/ });
export const Try = createToken({ name: "Try", pattern: /try\b/ });
export const Catch = createToken({ name: "Catch", pattern: /catch\b/ });
export const Finally = createToken({ name: "Finally", pattern: /finally\b/ });
export const Throw = createToken({ name: "Throw", pattern: /throw\b/ });
export const Null = createToken({ name: "Null", pattern: /null\b/ });
export const True = createToken({ name: "True", pattern: /true\b/ });
export const False = createToken({ name: "False", pattern: /false\b/ });
export const NotImplemented = createToken({
name: "NotImplemented",
pattern: /\?\?\?/,
});
export const Type = createToken({ name: "Type", pattern: /type\b/ });
export const Private = createToken({ name: "Private", pattern: /private\b/ });
export const Protected = createToken({
name: "Protected",
pattern: /protected\b/,
});
export const Public = createToken({ name: "Public", pattern: /public\b/ });
export const Abstract = createToken({
name: "Abstract",
pattern: /abstract\b/,
});
export const Final = createToken({ name: "Final", pattern: /final\b/ });
export const Sealed = createToken({ name: "Sealed", pattern: /sealed\b/ });
export const Implicit = createToken({
name: "Implicit",
pattern: /implicit\b/,
});
export const Lazy = createToken({ name: "Lazy", pattern: /lazy\b/ });
export const Override = createToken({
name: "Override",
pattern: /override\b/,
});
export const Given = createToken({ name: "Given", pattern: /given\b/ });
export const Using = createToken({ name: "Using", pattern: /using\b/ });
export const To = createToken({ name: "To", pattern: /to\b/ });
export const Enum = createToken({ name: "Enum", pattern: /enum\b/ });
export const Array = createToken({ name: "Array", pattern: /Array\b/ });
export const Extension = createToken({
name: "Extension",
pattern: /extension\b/,
});
export const Export = createToken({ name: "Export", pattern: /export\b/ });
export const Opaque = createToken({ name: "Opaque", pattern: /opaque\b/ });
export const Inline = createToken({ name: "Inline", pattern: /inline\b/ });
export const Transparent = createToken({
name: "Transparent",
pattern: /transparent\b/,
});
// Identifiers (must come after keywords)
// Enhanced Unicode identifier support following Scala Language Specification
// Operator identifier for custom operators (e.g., +++, <~>, etc.)
export const OperatorIdentifier = createToken({
name: "OperatorIdentifier",
pattern: /[+\-*/%:&|^<>=!~?#@$\\]+/,
});
// Backward compatible with existing implementation, enhanced mathematical symbol support
// Supports: Latin, Greek, Cyrillic, CJK, Arabic, Hebrew, Mathematical symbols, Emojis (via surrogate pairs)
export const Identifier = createToken({
name: "Identifier",
pattern:
/(?:_[a-zA-Z0-9_$\u00C0-\u00FF\u0370-\u03FF\u0400-\u04FF\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u0590-\u05FF\u0600-\u06FF\u2200-\u22FF\u27C0-\u27EF\u2980-\u29FF\u2A00-\u2AFF]+|[a-zA-Z$\u00C0-\u00FF\u0370-\u03FF\u0400-\u04FF\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u0590-\u05FF\u0600-\u06FF\u2200-\u22FF\u27C0-\u27EF\u2980-\u29FF\u2A00-\u2AFF][a-zA-Z0-9_$\u00C0-\u00FF\u0370-\u03FF\u0400-\u04FF\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u0590-\u05FF\u0600-\u06FF\u2200-\u22FF\u27C0-\u27EF\u2980-\u29FF\u2A00-\u2AFF]*)/u,
});
// Literals
export const IntegerLiteral = createToken({
name: "IntegerLiteral",
pattern: /-?\d+[lLiIsSbB]?/,
});
// Scientific notation literal (must come before FloatingPointLiteral)
export const ScientificNotationLiteral = createToken({
name: "ScientificNotationLiteral",
pattern: /-?\d+(\.\d+)?[eE][+-]?\d+[fFdD]?/,
});
export const FloatingPointLiteral = createToken({
name: "FloatingPointLiteral",
pattern: /-?\d+\.\d+[fFdD]?|-?\.\d+[fFdD]?/,
});
export const StringLiteral = createToken({
name: "StringLiteral",
pattern: /"""[\s\S]*?"""|"([^"\\]|\\.|\\u[0-9A-Fa-f]{4})*"/,
});
export const InterpolatedStringLiteral = createToken({
name: "InterpolatedStringLiteral",
pattern:
/[a-zA-Z_][a-zA-Z0-9_]*"""[\s\S]*?"""|[a-zA-Z_][a-zA-Z0-9_]*"([^"\\]|\\.|\\u[0-9A-Fa-f]{4}|\$[a-zA-Z_][a-zA-Z0-9_]*|\$\{[^}]*\})*"/,
});
export const CharLiteral = createToken({
name: "CharLiteral",
pattern: /'([^'\\]|\\.|\\u[0-9A-Fa-f]{4})'/,
});
// Operators
export const Equals = createToken({ name: "Equals", pattern: /=/ });
export const Plus = createToken({ name: "Plus", pattern: /\+/ });
export const Minus = createToken({ name: "Minus", pattern: /-/ });
export const Star = createToken({ name: "Star", pattern: /\*/ });
export const Slash = createToken({ name: "Slash", pattern: /\// });
export const Backslash = createToken({ name: "Backslash", pattern: /\\/ });
export const Percent = createToken({ name: "Percent", pattern: /%/ });
export const LessThan = createToken({ name: "LessThan", pattern: /</ });
export const GreaterThan = createToken({ name: "GreaterThan", pattern: />/ });
export const LessThanEquals = createToken({
name: "LessThanEquals",
pattern: /<=/,
});
export const GreaterThanEquals = createToken({
name: "GreaterThanEquals",
pattern: />=/,
});
export const EqualsEquals = createToken({
name: "EqualsEquals",
pattern: /==/,
});
export const DoubleEquals = EqualsEquals; // Alias for modular parser compatibility
export const NotEquals = createToken({ name: "NotEquals", pattern: /!=/ });
export const LogicalAnd = createToken({ name: "LogicalAnd", pattern: /&&/ });
export const LogicalOr = createToken({ name: "LogicalOr", pattern: /\|\|/ });
export const Exclamation = createToken({ name: "Exclamation", pattern: /!/ });
export const Arrow = createToken({ name: "Arrow", pattern: /=>/ });
export const TypeLambdaArrow = createToken({
name: "TypeLambdaArrow",
pattern: /=>>/,
});
export const DoubleArrow = TypeLambdaArrow; // Alias for modular parser compatibility
export const LeftArrow = createToken({ name: "LeftArrow", pattern: /<-/ });
export const RightArrow = createToken({ name: "RightArrow", pattern: /->/ });
export const ContextArrow = createToken({
name: "ContextArrow",
pattern: /\?=>/,
});
export const SubtypeOf = createToken({ name: "SubtypeOf", pattern: /<:/ });
export const ColonLess = SubtypeOf; // Alias for modular parser compatibility
export const SupertypeOf = createToken({ name: "SupertypeOf", pattern: />:/ });
export const GreaterColon = SupertypeOf; // Alias for modular parser compatibility
export const AppendOp = createToken({ name: "AppendOp", pattern: /:\+/ });
export const PlusColon = AppendOp; // Alias for modular parser compatibility
export const ColonPlus = createToken({ name: "ColonPlus", pattern: /:\+/ }); // Same as AppendOp but separate token for parser
export const PrependOp = createToken({ name: "PrependOp", pattern: /::/ });
export const ColonColon = PrependOp; // Alias for modular parser compatibility
export const ConcatOp = createToken({ name: "ConcatOp", pattern: /\+\+/ });
export const DoublePlus = ConcatOp; // Alias for modular parser compatibility
export const AppendEquals = createToken({
name: "AppendEquals",
pattern: /\+\+=/,
});
// Compound assignment operators
export const PlusEquals = createToken({ name: "PlusEquals", pattern: /\+=/ });
export const MinusEquals = createToken({ name: "MinusEquals", pattern: /-=/ });
export const StarEquals = createToken({ name: "StarEquals", pattern: /\*=/ });
export const SlashEquals = createToken({ name: "SlashEquals", pattern: /\/=/ });
export const PercentEquals = createToken({
name: "PercentEquals",
pattern: /%=/,
});
// sbt DSL operators
export const DoublePercent = createToken({
name: "DoublePercent",
pattern: /%%/,
});
// Bitwise operators
export const BitwiseAnd = createToken({ name: "BitwiseAnd", pattern: /&/ });
export const BitwiseOr = createToken({ name: "BitwiseOr", pattern: /\|/ });
export const BitwiseXor = createToken({ name: "BitwiseXor", pattern: /\^/ });
export const BitwiseTilde = createToken({ name: "BitwiseTilde", pattern: /~/ });
export const LeftShift = createToken({ name: "LeftShift", pattern: /<</ });
export const RightShift = createToken({ name: "RightShift", pattern: />>/ });
export const UnsignedRightShift = createToken({
name: "UnsignedRightShift",
pattern: />>>/,
});
export const Colon = createToken({ name: "Colon", pattern: /:/ });
export const ColonEquals = createToken({ name: "ColonEquals", pattern: /:=/ });
export const SbtAssign = ColonEquals; // Alias for sbt compatibility
export const Semicolon = createToken({ name: "Semicolon", pattern: /;/ });
export const Comma = createToken({ name: "Comma", pattern: /,/ });
export const Dot = createToken({ name: "Dot", pattern: /\./ });
export const Underscore = createToken({
name: "Underscore",
pattern: /_/,
});
export const At = createToken({ name: "At", pattern: /@/ });
export const Question = createToken({ name: "Question", pattern: /\?/ });
// Quote and Splice tokens for Scala 3 macros
export const QuoteStart = createToken({ name: "QuoteStart", pattern: /'\{/ });
export const SpliceStart = createToken({
name: "SpliceStart",
pattern: /\$\{/,
});
// Additional tokens for modular parser
export const Quote = createToken({ name: "Quote", pattern: /'/ });
export const Dollar = createToken({ name: "Dollar", pattern: /\$/ });
// QuestionArrow is now alias for ContextArrow to avoid duplicate patterns
export const QuestionArrow = ContextArrow;
// String interpolation tokens
export const InterpolatedString = createToken({
name: "InterpolatedString",
pattern: /s"([^"\\]|\\.|\\u[0-9A-Fa-f]{4})*"/,
});
export const FormattedString = createToken({
name: "FormattedString",
pattern: /f"([^"\\]|\\.|\\u[0-9A-Fa-f]{4})*"/,
});
export const RawString = createToken({
name: "RawString",
pattern: /raw"([^"\\]|\\.|\\u[0-9A-Fa-f]{4})*"/,
});
export const CustomInterpolatedString = createToken({
name: "CustomInterpolatedString",
pattern: /[a-zA-Z_][a-zA-Z0-9_]*"([^"\\]|\\.|\\u[0-9A-Fa-f]{4})*"/,
});
// Numeric suffix tokens
export const LongSuffix = createToken({ name: "LongSuffix", pattern: /[lL]/ });
export const IntSuffix = createToken({ name: "IntSuffix", pattern: /[iI]/ });
export const ShortSuffix = createToken({
name: "ShortSuffix",
pattern: /[sS]/,
});
export const ByteSuffix = createToken({ name: "ByteSuffix", pattern: /[bB]/ });
export const FloatSuffix = createToken({
name: "FloatSuffix",
pattern: /[fF]/,
});
export const DoubleSuffix = createToken({
name: "DoubleSuffix",
pattern: /[dD]/,
});
// Additional missing tokens
export const Hash = createToken({ name: "Hash", pattern: /#/ });
// Delimiters
export const LeftParen = createToken({ name: "LeftParen", pattern: /\(/ });
export const RightParen = createToken({ name: "RightParen", pattern: /\)/ });
export const LeftBracket = createToken({ name: "LeftBracket", pattern: /\[/ });
export const RightBracket = createToken({
name: "RightBracket",
pattern: /\]/,
});
export const LeftBrace = createToken({ name: "LeftBrace", pattern: /\{/ });
export const RightBrace = createToken({ name: "RightBrace", pattern: /\}/ });
// Whitespace and Comments
export const WhiteSpace = createToken({
name: "WhiteSpace",
pattern: /\s+/,
group: Lexer.SKIPPED,
});
export const LineComment = createToken({
name: "LineComment",
pattern: /\/\/[^\n\r]*/,
group: "comments",
});
export const BlockComment = createToken({
name: "BlockComment",
pattern: /\/\*([^*]|\*(?!\/))*\*\//,
group: "comments",
});
// All tokens in order
export const allTokens = [
// Comments (must come before operators)
LineComment,
BlockComment,
// Whitespace
WhiteSpace,
// Keywords (must come before Identifier)
Val,
Var,
Def,
Class,
ObjectKeyword,
Trait,
Extends,
With,
If,
Else,
While,
For,
Yield,
Return,
New,
This,
Super,
Package,
Import,
Case,
Match,
Try,
Catch,
Finally,
Throw,
Null,
True,
False,
NotImplemented,
Type,
Private,
Protected,
Public,
Abstract,
Final,
Sealed,
Implicit,
Lazy,
Override,
Given,
Using,
To,
Enum,
Array,
Extension,
Export,
Opaque,
Inline,
Transparent,
// Literals
ScientificNotationLiteral, // Must come before FloatingPointLiteral
FloatingPointLiteral, // Must come before IntegerLiteral
IntegerLiteral,
// String interpolation literals (must come before StringLiteral)
CustomInterpolatedString,
InterpolatedString,
FormattedString,
RawString,
InterpolatedStringLiteral, // Must come before StringLiteral
StringLiteral,
CharLiteral,
// Multi-character operators (must come before single-character)
TypeLambdaArrow, // Must come before Arrow to avoid ambiguity
ContextArrow, // Must come before Arrow to avoid ambiguity
Arrow,
LeftArrow,
RightArrow,
SubtypeOf,
SupertypeOf,
LessThanEquals,
GreaterThanEquals,
EqualsEquals,
NotEquals,
LogicalAnd,
LogicalOr,
ColonEquals, // := must come before :
AppendOp,
PrependOp,
AppendEquals, // ++= must come before ++
ConcatOp,
// Quote and splice tokens (must come before single-character)
QuoteStart, // '{ must come before single '
SpliceStart, // ${ must come before single $
// Compound assignment operators
PlusEquals,
MinusEquals,
StarEquals,
SlashEquals,
PercentEquals,
// Bitwise shift operators (must come before single-character)
UnsignedRightShift, // >>> must come before >>
LeftShift,
RightShift,
// Single-character operators
Equals,
Plus,
Minus,
Star,
Slash,
Backslash,
DoublePercent, // %% must come before single %
Percent,
LessThan,
GreaterThan,
Exclamation,
BitwiseAnd,
BitwiseOr,
BitwiseXor,
BitwiseTilde,
Colon,
Semicolon,
Comma,
Dot,
At,
// QuestionArrow removed - now an alias for ContextArrow
Question,
Quote,
Dollar,
Hash,
// Delimiters
LeftParen,
RightParen,
LeftBracket,
RightBracket,
LeftBrace,
RightBrace,
// Operator identifier (before regular identifier)
OperatorIdentifier,
// Identifier (must come before underscore)
Identifier,
// Underscore (must come after identifier to not interfere with _identifier patterns)
Underscore,
];
// レキサーの作成(インポート時の問題を回避するための遅延初期化)
let scalaLexerInstance: Lexer | null = null;
/**
* Scalaコードの字句解析を行うレキサー
*/
export const ScalaLexer = {
/**
* レキサーインスタンスを取得(遅延初期化)
* @returns Chevrotainレキサーのインスタンス
*/
get instance(): Lexer {
if (!scalaLexerInstance) {
scalaLexerInstance = new Lexer(allTokens);
}
return scalaLexerInstance;
},
/**
* 入力文字列をトークン化
* @param input - 字句解析対象のScalaソースコード
* @returns トークン化の結果(トークン、エラー、グループ化されたトークン)
*/
tokenize(input: string): ILexingResult {
return this.instance.tokenize(input);
},
};
// Export lexer instance for backward compatibility with tests
export const lexerInstance = ScalaLexer;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
/**
* Base parser module with shared utilities and interfaces
*/
import * as tokens from "../lexer";
import { CstParser, ParserMethod, CstNode } from "chevrotain";
import type { TokenType } from "chevrotain";
export interface ParserRuleMixin {
// Utility methods for parser rules - these need to match CstParser access levels
RULE: CstParser["RULE"];
SUBRULE: CstParser["SUBRULE"];
CONSUME: CstParser["CONSUME"];
MANY: CstParser["MANY"];
MANY_SEP: CstParser["MANY_SEP"];
OPTION: CstParser["OPTION"];
OR: CstParser["OR"];
AT_LEAST_ONE: CstParser["AT_LEAST_ONE"];
AT_LEAST_ONE_SEP: CstParser["AT_LEAST_ONE_SEP"];
LA: CstParser["LA"];
performSelfAnalysis: CstParser["performSelfAnalysis"];
}
export abstract class BaseParserModule {
protected parser: ParserRuleMixin;
constructor(parser: ParserRuleMixin) {
this.parser = parser;
}
// Helper methods for common patterns
protected consumeTokenType(tokenType: TokenType) {
return this.parser.CONSUME(tokenType);
}
protected optionalConsume(tokenType: TokenType) {
return this.parser.OPTION(() => this.parser.CONSUME(tokenType));
}
protected manyOf(rule: () => void) {
return this.parser.MANY(rule);
}
protected oneOf(
alternatives: Array<{ ALT: () => void; GATE?: () => boolean }>,
) {
return this.parser.OR(alternatives);
}
protected subrule(rule: ParserMethod<unknown[], CstNode>) {
return this.parser.SUBRULE(rule);
}
protected lookahead(offset: number) {
return this.parser.LA(offset);
}
}
export { tokens };

View File

@@ -0,0 +1,179 @@
/**
* Definition parsing module for class, object, trait, method, and variable definitions
*/
import { BaseParserModule, tokens } from "./base";
import type { ParserMethod, CstNode } from "chevrotain";
export class DefinitionParserMixin extends BaseParserModule {
// Dependencies from other modules
annotation!: ParserMethod<unknown[], CstNode>;
modifier!: ParserMethod<unknown[], CstNode>;
typeParameters!: ParserMethod<unknown[], CstNode>;
classParameters!: ParserMethod<unknown[], CstNode>;
extendsClause!: ParserMethod<unknown[], CstNode>;
classBody!: ParserMethod<unknown[], CstNode>;
type!: ParserMethod<unknown[], CstNode>;
expression!: ParserMethod<unknown[], CstNode>;
pattern!: ParserMethod<unknown[], CstNode>;
parameterLists!: ParserMethod<unknown[], CstNode>;
// Class definition
classDefinition = this.parser.RULE("classDefinition", () => {
this.consumeTokenType(tokens.Class);
this.consumeTokenType(tokens.Identifier);
this.parser.OPTION(() => this.subrule(this.typeParameters));
// Constructor annotations (for DI patterns like @Inject())
this.manyOf(() => this.subrule(this.annotation));
// Constructor parameters (multiple parameter lists supported)
this.parser.MANY(() => this.subrule(this.classParameters));
this.parser.OPTION(() => this.subrule(this.extendsClause));
this.parser.OPTION(() => this.subrule(this.classBody));
});
// Object definition
objectDefinition = this.parser.RULE("objectDefinition", () => {
this.consumeTokenType(tokens.ObjectKeyword);
this.consumeTokenType(tokens.Identifier);
this.parser.OPTION(() => this.subrule(this.extendsClause));
this.parser.OPTION(() => this.subrule(this.classBody));
});
// Trait definition
traitDefinition = this.parser.RULE("traitDefinition", () => {
this.consumeTokenType(tokens.Trait);
this.consumeTokenType(tokens.Identifier);
this.parser.OPTION(() => this.subrule(this.typeParameters));
this.parser.OPTION(() => this.subrule(this.extendsClause));
this.parser.OPTION(() => this.subrule(this.classBody));
});
// Enum definition (Scala 3)
enumDefinition = this.parser.RULE("enumDefinition", () => {
this.consumeTokenType(tokens.Enum);
this.consumeTokenType(tokens.Identifier);
this.parser.OPTION(() => this.subrule(this.typeParameters));
this.parser.OPTION(() => this.subrule(this.classParameters));
this.parser.OPTION(() => this.subrule(this.extendsClause));
this.consumeTokenType(tokens.LeftBrace);
this.manyOf(() => this.subrule(this.enumCaseDef));
this.consumeTokenType(tokens.RightBrace);
});
enumCaseDef = this.parser.RULE("enumCaseDef", () => {
this.consumeTokenType(tokens.Case);
this.consumeTokenType(tokens.Identifier);
this.parser.OPTION(() => this.subrule(this.classParameters));
this.parser.OPTION(() => this.subrule(this.extendsClause));
this.parser.OPTION(() => this.consumeTokenType(tokens.Semicolon));
});
// Extension definition (Scala 3)
extensionDefinition = this.parser.RULE("extensionDefinition", () => {
this.consumeTokenType(tokens.Extension);
this.parser.OPTION(() => this.subrule(this.typeParameters));
this.consumeTokenType(tokens.LeftParen);
this.consumeTokenType(tokens.Identifier);
this.consumeTokenType(tokens.Colon);
this.subrule(this.type);
this.consumeTokenType(tokens.RightParen);
this.consumeTokenType(tokens.LeftBrace);
this.manyOf(() => this.subrule(this.extensionMemberDef));
this.consumeTokenType(tokens.RightBrace);
});
extensionMemberDef = this.parser.RULE("extensionMemberDef", () => {
this.manyOf(() => this.subrule(this.modifier));
this.subrule(this.defDefinition);
});
// Val definition
valDefinition = this.parser.RULE("valDefinition", () => {
this.consumeTokenType(tokens.Val);
this.oneOf([
{
// Simple variable with optional type: val x: Type = expr or val x: Type (abstract)
ALT: () => {
this.consumeTokenType(tokens.Identifier);
this.parser.OPTION(() => {
this.consumeTokenType(tokens.Colon);
this.subrule(this.type);
});
this.parser.OPTION(() => {
this.consumeTokenType(tokens.Equals);
this.subrule(this.expression);
});
},
GATE: () => {
// This alternative is for simple identifier patterns only
// Must handle: val x = ..., val x: Type = ..., val x: Type (abstract)
// Must NOT handle: val (x, y) = ..., val SomeClass(...) = ...
const first = this.lookahead(1);
const second = this.lookahead(2);
// If first token is not identifier, this is not a simple val
if (!first || first.tokenType !== tokens.Identifier) return false;
// If second token is left paren, this is a constructor pattern
if (second && second.tokenType === tokens.LeftParen) return false;
// Otherwise, this is a simple identifier (with or without type, with or without assignment)
return true;
},
},
{
// Pattern matching: val (x, y) = expr or val SomeClass(...) = expr
ALT: () => {
this.subrule(this.pattern);
this.consumeTokenType(tokens.Equals);
this.subrule(this.expression);
},
},
]);
this.parser.OPTION(() => this.consumeTokenType(tokens.Semicolon));
});
// Var definition
varDefinition = this.parser.RULE("varDefinition", () => {
this.consumeTokenType(tokens.Var);
this.consumeTokenType(tokens.Identifier);
this.parser.OPTION(() => {
this.consumeTokenType(tokens.Colon);
this.subrule(this.type);
});
this.consumeTokenType(tokens.Equals);
this.subrule(this.expression);
this.parser.OPTION(() => this.consumeTokenType(tokens.Semicolon));
});
// Method definition
defDefinition = this.parser.RULE("defDefinition", () => {
this.consumeTokenType(tokens.Def);
this.oneOf([
// Regular method name
{ ALT: () => this.consumeTokenType(tokens.Identifier) },
// Constructor (this keyword)
{ ALT: () => this.consumeTokenType(tokens.This) },
]);
this.parser.OPTION(() => this.subrule(this.typeParameters));
this.parser.OPTION(() => this.subrule(this.parameterLists));
this.parser.OPTION(() => {
this.consumeTokenType(tokens.Colon);
this.subrule(this.type);
});
this.parser.OPTION(() => {
this.consumeTokenType(tokens.Equals);
this.subrule(this.expression);
});
this.parser.OPTION(() => this.consumeTokenType(tokens.Semicolon));
});
// Type definition
typeDefinition = this.parser.RULE("typeDefinition", () => {
this.consumeTokenType(tokens.Type);
this.consumeTokenType(tokens.Identifier);
this.parser.OPTION(() => this.subrule(this.typeParameters));
this.consumeTokenType(tokens.Equals);
this.subrule(this.type);
this.parser.OPTION(() => this.consumeTokenType(tokens.Semicolon));
});
}

View File

@@ -0,0 +1,425 @@
/**
* Expression parsing module for all types of expressions in Scala
*/
import { BaseParserModule, tokens } from "./base";
import type { ParserMethod, CstNode } from "chevrotain";
export class ExpressionParserMixin extends BaseParserModule {
// Dependencies from other modules
annotation!: ParserMethod<unknown[], CstNode>;
modifier!: ParserMethod<unknown[], CstNode>;
type!: ParserMethod<unknown[], CstNode>;
literal!: ParserMethod<unknown[], CstNode>;
qualifiedIdentifier!: ParserMethod<unknown[], CstNode>;
pattern!: ParserMethod<unknown[], CstNode>;
parameterLists!: ParserMethod<unknown[], CstNode>;
typeArgument!: ParserMethod<unknown[], CstNode>;
caseClause!: ParserMethod<unknown[], CstNode>;
generator!: ParserMethod<unknown[], CstNode>;
// Main expression rule
expression = this.parser.RULE("expression", () => {
this.parser.OR([
// Polymorphic function literal (Scala 3)
{
ALT: () => this.subrule(this.polymorphicFunctionLiteral),
GATE: () => {
const la1 = this.parser.LA(1);
return la1?.tokenType === tokens.LeftBracket;
},
},
// Regular expressions
{ ALT: () => this.subrule(this.assignmentOrInfixExpression) },
]);
});
// Assignment or infix expression
assignmentOrInfixExpression = this.parser.RULE(
"assignmentOrInfixExpression",
() => {
this.subrule(this.postfixExpression);
this.parser.MANY(() => {
this.subrule(this.infixOperator);
this.subrule(this.postfixExpression);
});
},
);
// Postfix expression
postfixExpression = this.parser.RULE("postfixExpression", () => {
this.subrule(this.primaryExpression);
this.parser.MANY(() => {
this.parser.OR([
// Method call with parentheses
{
ALT: () => {
this.consumeTokenType(tokens.LeftParen);
this.parser.MANY_SEP({
SEP: tokens.Comma,
DEF: () => this.subrule(this.expression),
});
this.consumeTokenType(tokens.RightParen);
},
},
// Type arguments
{
ALT: () => {
this.consumeTokenType(tokens.LeftBracket);
this.parser.MANY_SEP({
SEP: tokens.Comma,
DEF: () => this.subrule(this.typeArgument),
});
this.consumeTokenType(tokens.RightBracket);
},
},
// Member access
{
ALT: () => {
this.consumeTokenType(tokens.Dot);
this.consumeTokenType(tokens.Identifier);
},
},
// Postfix operator (like Ask pattern ?)
{
ALT: () => {
this.consumeTokenType(tokens.Question);
},
},
]);
});
});
// Primary expression
primaryExpression = this.parser.RULE("primaryExpression", () => {
this.parser.OR([
// Literals
{ ALT: () => this.subrule(this.literal) },
// Identifier
{ ALT: () => this.consumeTokenType(tokens.Identifier) },
// This and super
{ ALT: () => this.consumeTokenType(tokens.This) },
{ ALT: () => this.consumeTokenType(tokens.Super) },
// Underscore (placeholder)
{ ALT: () => this.consumeTokenType(tokens.Underscore) },
// Parenthesized expression
{
ALT: () => {
this.consumeTokenType(tokens.LeftParen);
this.parser.OPTION(() => this.subrule(this.expression));
this.consumeTokenType(tokens.RightParen);
},
},
// Block expression
{ ALT: () => this.subrule(this.blockExpression) },
// New expression
{ ALT: () => this.subrule(this.newExpression) },
// Partial function literal
{ ALT: () => this.subrule(this.partialFunctionLiteral) },
// Quote expression (Scala 3)
{ ALT: () => this.subrule(this.quoteExpression) },
// Splice expression (Scala 3)
{ ALT: () => this.subrule(this.spliceExpression) },
// If expression
{ ALT: () => this.subrule(this.ifExpression) },
// While expression
{ ALT: () => this.subrule(this.whileExpression) },
// Try expression
{ ALT: () => this.subrule(this.tryExpression) },
// For expression
{ ALT: () => this.subrule(this.forExpression) },
// Match expression
{
ALT: () => {
this.subrule(this.expression);
this.consumeTokenType(tokens.Match);
this.consumeTokenType(tokens.LeftBrace);
this.parser.MANY(() => this.subrule(this.caseClause));
this.consumeTokenType(tokens.RightBrace);
},
},
// Lambda expression
{
ALT: () => {
this.parser.OR([
// Simple identifier lambda: x =>
{
ALT: () => {
this.consumeTokenType(tokens.Identifier);
},
},
// Multiple parameters with optional types: (x, y) =>
{
ALT: () => {
this.consumeTokenType(tokens.LeftParen);
this.parser.MANY_SEP({
SEP: tokens.Comma,
DEF: () => {
this.consumeTokenType(tokens.Identifier);
this.parser.OPTION(() => {
this.consumeTokenType(tokens.Colon);
this.subrule(this.type);
});
},
});
this.consumeTokenType(tokens.RightParen);
},
},
]);
this.consumeTokenType(tokens.Arrow);
this.subrule(this.expression);
},
GATE: () => {
const la1 = this.parser.LA(1);
const la2 = this.parser.LA(2);
// Simple lambda: identifier =>
if (
la1?.tokenType === tokens.Identifier &&
la2?.tokenType === tokens.Arrow
) {
return true;
}
// Parenthesized lambda: ( ... ) =>
if (la1?.tokenType === tokens.LeftParen) {
let i = 2;
let parenCount = 1;
while (parenCount > 0 && this.parser.LA(i)) {
const token = this.parser.LA(i);
if (token?.tokenType === tokens.LeftParen) parenCount++;
if (token?.tokenType === tokens.RightParen) parenCount--;
i++;
}
return this.parser.LA(i)?.tokenType === tokens.Arrow;
}
return false;
},
},
]);
});
// Infix operator
infixOperator = this.parser.RULE("infixOperator", () => {
this.parser.OR([
// Special compound assignment operators
{ ALT: () => this.consumeTokenType(tokens.PlusEquals) },
{ ALT: () => this.consumeTokenType(tokens.MinusEquals) },
{ ALT: () => this.consumeTokenType(tokens.StarEquals) },
{ ALT: () => this.consumeTokenType(tokens.SlashEquals) },
{ ALT: () => this.consumeTokenType(tokens.PercentEquals) },
{ ALT: () => this.consumeTokenType(tokens.AppendEquals) },
// sbt-specific operators
{ ALT: () => this.consumeTokenType(tokens.SbtAssign) },
{ ALT: () => this.consumeTokenType(tokens.DoublePercent) },
// Basic operators
{ ALT: () => this.consumeTokenType(tokens.Plus) },
{ ALT: () => this.consumeTokenType(tokens.Minus) },
{ ALT: () => this.consumeTokenType(tokens.Star) },
{ ALT: () => this.consumeTokenType(tokens.Slash) },
{ ALT: () => this.consumeTokenType(tokens.Percent) },
// Comparison operators
{ ALT: () => this.consumeTokenType(tokens.Equals) },
{ ALT: () => this.consumeTokenType(tokens.EqualsEquals) }, // Use EqualsEquals instead of DoubleEquals
{ ALT: () => this.consumeTokenType(tokens.NotEquals) },
{ ALT: () => this.consumeTokenType(tokens.LessThan) }, // Use LessThan instead of Less
{ ALT: () => this.consumeTokenType(tokens.GreaterThan) }, // Use GreaterThan instead of Greater
{ ALT: () => this.consumeTokenType(tokens.LessThanEquals) }, // Use LessThanEquals instead of LessEquals
{ ALT: () => this.consumeTokenType(tokens.GreaterThanEquals) }, // Use GreaterThanEquals instead of GreaterEquals
// Logical operators
{ ALT: () => this.consumeTokenType(tokens.LogicalAnd) }, // Use LogicalAnd instead of DoubleAmpersand
{ ALT: () => this.consumeTokenType(tokens.LogicalOr) }, // Use LogicalOr instead of DoublePipe
// Bitwise operators
{ ALT: () => this.consumeTokenType(tokens.BitwiseAnd) }, // Use BitwiseAnd instead of Ampersand
{ ALT: () => this.consumeTokenType(tokens.BitwiseOr) }, // Use BitwiseOr instead of Pipe
{ ALT: () => this.consumeTokenType(tokens.BitwiseXor) }, // Use BitwiseXor instead of Caret
// Shift operators
{ ALT: () => this.consumeTokenType(tokens.LeftShift) }, // Use LeftShift instead of DoubleLeftAngle
{ ALT: () => this.consumeTokenType(tokens.RightShift) }, // Use RightShift instead of DoubleRightAngle
{ ALT: () => this.consumeTokenType(tokens.UnsignedRightShift) }, // Use UnsignedRightShift instead of TripleRightAngle
// Type operators
{ ALT: () => this.consumeTokenType(tokens.Colon) },
{ ALT: () => this.consumeTokenType(tokens.ColonEquals) },
// Collection operators
{ ALT: () => this.consumeTokenType(tokens.ConcatOp) }, // Use ConcatOp instead of DoublePlus
{ ALT: () => this.consumeTokenType(tokens.PrependOp) }, // Use PrependOp instead of ColonColon
{ ALT: () => this.consumeTokenType(tokens.AppendOp) }, // Use AppendOp instead of ColonPlus/PlusColon
// XML operators
{ ALT: () => this.consumeTokenType(tokens.Backslash) },
// General operator
{ ALT: () => this.consumeTokenType(tokens.OperatorIdentifier) },
// Identifier as operator (for named methods used as infix)
{
ALT: () => this.consumeTokenType(tokens.Identifier),
},
]);
});
// Polymorphic function literal (Scala 3)
polymorphicFunctionLiteral = this.parser.RULE(
"polymorphicFunctionLiteral",
() => {
this.consumeTokenType(tokens.LeftBracket);
this.parser.MANY_SEP({
SEP: tokens.Comma,
DEF: () => this.subrule(this.polymorphicTypeParameter),
});
this.consumeTokenType(tokens.RightBracket);
this.consumeTokenType(tokens.Arrow);
this.subrule(this.expression);
},
);
// New expression
newExpression = this.parser.RULE("newExpression", () => {
this.consumeTokenType(tokens.New);
this.parser.OR([
// New with class instantiation
{
ALT: () => {
this.subrule(this.type);
this.parser.MANY(() => {
this.consumeTokenType(tokens.LeftParen);
this.parser.MANY_SEP({
SEP: tokens.Comma,
DEF: () => this.subrule(this.expression),
});
this.consumeTokenType(tokens.RightParen);
});
},
},
// New with anonymous class
{
ALT: () => {
this.consumeTokenType(tokens.LeftBrace);
// Class body content
this.consumeTokenType(tokens.RightBrace);
},
},
]);
});
// Block expression
blockExpression = this.parser.RULE("blockExpression", () => {
this.consumeTokenType(tokens.LeftBrace);
this.parser.MANY(() => {
this.subrule(this.blockStatement);
this.parser.OPTION(() => this.consumeTokenType(tokens.Semicolon));
});
this.consumeTokenType(tokens.RightBrace);
});
// Partial function literal
partialFunctionLiteral = this.parser.RULE("partialFunctionLiteral", () => {
this.consumeTokenType(tokens.LeftBrace);
this.parser.AT_LEAST_ONE(() => this.subrule(this.caseClause));
this.consumeTokenType(tokens.RightBrace);
});
// Quote expression (Scala 3)
quoteExpression = this.parser.RULE("quoteExpression", () => {
this.consumeTokenType(tokens.Quote);
this.consumeTokenType(tokens.LeftBrace);
this.subrule(this.expression);
this.consumeTokenType(tokens.RightBrace);
});
// Splice expression (Scala 3)
spliceExpression = this.parser.RULE("spliceExpression", () => {
this.consumeTokenType(tokens.Dollar);
this.consumeTokenType(tokens.LeftBrace);
this.subrule(this.expression);
this.consumeTokenType(tokens.RightBrace);
});
// If expression
ifExpression = this.parser.RULE("ifExpression", () => {
this.consumeTokenType(tokens.If);
this.consumeTokenType(tokens.LeftParen);
this.subrule(this.expression);
this.consumeTokenType(tokens.RightParen);
this.subrule(this.expression);
this.parser.OPTION(() => {
this.consumeTokenType(tokens.Else);
this.subrule(this.expression);
});
});
// While expression
whileExpression = this.parser.RULE("whileExpression", () => {
this.consumeTokenType(tokens.While);
this.consumeTokenType(tokens.LeftParen);
this.subrule(this.expression);
this.consumeTokenType(tokens.RightParen);
this.subrule(this.expression);
});
// Try expression
tryExpression = this.parser.RULE("tryExpression", () => {
this.consumeTokenType(tokens.Try);
this.subrule(this.expression);
this.parser.OPTION(() => {
this.consumeTokenType(tokens.Catch);
this.parser.OR([
// Pattern-based catch
{
ALT: () => {
this.consumeTokenType(tokens.LeftBrace);
this.parser.MANY(() => this.subrule(this.caseClause));
this.consumeTokenType(tokens.RightBrace);
},
},
// Expression-based catch
{
ALT: () => this.subrule(this.expression),
},
]);
});
this.parser.OPTION(() => {
this.consumeTokenType(tokens.Finally);
this.subrule(this.expression);
});
});
// For expression/comprehension
forExpression = this.parser.RULE("forExpression", () => {
this.consumeTokenType(tokens.For);
this.parser.OR([
// For with parentheses
{
ALT: () => {
this.consumeTokenType(tokens.LeftParen);
this.parser.AT_LEAST_ONE_SEP({
SEP: tokens.Semicolon,
DEF: () => this.subrule(this.generator),
});
this.consumeTokenType(tokens.RightParen);
},
},
// For with braces
{
ALT: () => {
this.consumeTokenType(tokens.LeftBrace);
this.parser.MANY(() => this.subrule(this.generator));
this.consumeTokenType(tokens.RightBrace);
},
},
]);
this.parser.OPTION(() => this.consumeTokenType(tokens.Yield));
this.subrule(this.expression);
});
// Helper rule dependencies (to be implemented in other modules)
polymorphicTypeParameter = this.parser.RULE(
"polymorphicTypeParameter",
() => {
// Placeholder - should be in types.ts
this.consumeTokenType(tokens.Identifier);
},
);
blockStatement = this.parser.RULE("blockStatement", () => {
// Placeholder - should be in statements.ts
this.subrule(this.expression);
});
}

View File

@@ -0,0 +1,126 @@
/**
* Literal parsing module for all Scala literal types
*/
import { BaseParserModule, tokens } from "./base";
// Module for literal parsing - no additional imports needed
export class LiteralParserMixin extends BaseParserModule {
// Main literal rule
literal = this.parser.RULE("literal", () => {
this.parser.OR([
// Numeric literals
{ ALT: () => this.consumeTokenType(tokens.IntegerLiteral) },
{ ALT: () => this.consumeTokenType(tokens.FloatingPointLiteral) },
{ ALT: () => this.consumeTokenType(tokens.ScientificNotationLiteral) },
// Boolean literals
{ ALT: () => this.consumeTokenType(tokens.True) },
{ ALT: () => this.consumeTokenType(tokens.False) },
// Character literal
{ ALT: () => this.consumeTokenType(tokens.CharLiteral) },
// String literals
{ ALT: () => this.consumeTokenType(tokens.StringLiteral) },
{ ALT: () => this.consumeTokenType(tokens.InterpolatedStringLiteral) },
// Interpolated strings
{ ALT: () => this.subrule(this.interpolatedString) },
// Null literal
{ ALT: () => this.consumeTokenType(tokens.Null) },
// Unit literal ()
{
ALT: () => {
this.consumeTokenType(tokens.LeftParen);
this.consumeTokenType(tokens.RightParen);
},
},
]);
});
// Interpolated string
interpolatedString = this.parser.RULE("interpolatedString", () => {
this.parser.OR([
// s-interpolator
{ ALT: () => this.consumeTokenType(tokens.InterpolatedString) },
// f-interpolator
{ ALT: () => this.consumeTokenType(tokens.FormattedString) },
// raw-interpolator
{ ALT: () => this.consumeTokenType(tokens.RawString) },
// Custom interpolator
{ ALT: () => this.consumeTokenType(tokens.CustomInterpolatedString) },
]);
});
// Numeric literal with suffix
numericLiteral = this.parser.RULE("numericLiteral", () => {
this.parser.OR([
// Integer types
{
ALT: () => {
this.consumeTokenType(tokens.IntegerLiteral);
this.parser.OPTION(() => {
this.parser.OR([
{ ALT: () => this.consumeTokenType(tokens.LongSuffix) },
{ ALT: () => this.consumeTokenType(tokens.IntSuffix) },
{ ALT: () => this.consumeTokenType(tokens.ShortSuffix) },
{ ALT: () => this.consumeTokenType(tokens.ByteSuffix) },
]);
});
},
},
// Floating point types
{
ALT: () => {
this.consumeTokenType(tokens.FloatingPointLiteral);
this.parser.OPTION(() => {
this.parser.OR([
{ ALT: () => this.consumeTokenType(tokens.FloatSuffix) },
{ ALT: () => this.consumeTokenType(tokens.DoubleSuffix) },
]);
});
},
},
]);
});
// XML literal (if XML support is needed)
xmlLiteral = this.parser.RULE("xmlLiteral", () => {
// Placeholder for XML literals
// This would require XML-specific lexing
this.consumeTokenType(tokens.StringLiteral);
});
// Collection literal patterns (syntactic sugar)
collectionLiteral = this.parser.RULE("collectionLiteral", () => {
this.parser.OR([
// List literal: List(1, 2, 3)
{
ALT: () => {
this.consumeTokenType(tokens.Identifier); // List, Set, etc.
this.consumeTokenType(tokens.LeftParen);
this.parser.MANY_SEP({
SEP: tokens.Comma,
DEF: () => this.subrule(this.literal),
});
this.consumeTokenType(tokens.RightParen);
},
},
// Array literal: Array(1, 2, 3)
{
ALT: () => {
this.consumeTokenType(tokens.Array);
this.consumeTokenType(tokens.LeftParen);
this.parser.MANY_SEP({
SEP: tokens.Comma,
DEF: () => this.subrule(this.literal),
});
this.consumeTokenType(tokens.RightParen);
},
},
]);
});
}

View File

@@ -0,0 +1,243 @@
/**
* Pattern matching parsing module
*/
import { BaseParserModule, tokens } from "./base";
import type { ParserMethod, CstNode } from "chevrotain";
export class PatternParserMixin extends BaseParserModule {
// Dependencies from other modules
literal!: ParserMethod<unknown[], CstNode>;
qualifiedIdentifier!: ParserMethod<unknown[], CstNode>;
type!: ParserMethod<unknown[], CstNode>;
expression!: ParserMethod<unknown[], CstNode>;
// Pattern rule
pattern = this.parser.RULE("pattern", () => {
this.parser.OR([
// Wildcard pattern: _
{ ALT: () => this.consumeTokenType(tokens.Underscore) },
// Literal pattern
{ ALT: () => this.subrule(this.literal) },
// Variable pattern (lowercase identifier)
{
ALT: () => this.consumeTokenType(tokens.Identifier),
GATE: () => {
const la1 = this.parser.LA(1);
if (la1?.tokenType !== tokens.Identifier) return false;
const firstChar = la1.image[0];
return (
firstChar === firstChar.toLowerCase() &&
firstChar !== firstChar.toUpperCase()
);
},
},
// Stable identifier pattern (uppercase or qualified)
{
ALT: () => this.subrule(this.qualifiedIdentifier),
},
// Constructor pattern: Type(patterns...)
{
ALT: () => {
this.subrule(this.qualifiedIdentifier);
this.consumeTokenType(tokens.LeftParen);
this.parser.MANY_SEP({
SEP: tokens.Comma,
DEF: () => this.subrule(this.pattern),
});
this.consumeTokenType(tokens.RightParen);
},
GATE: () => {
// Look for Constructor(...)
let i = 1;
while (this.parser.LA(i)?.tokenType === tokens.Identifier) {
if (this.parser.LA(i + 1)?.tokenType === tokens.Dot) {
i += 2;
} else {
return this.parser.LA(i + 1)?.tokenType === tokens.LeftParen;
}
}
return false;
},
},
// Tuple pattern: (p1, p2, ...)
{
ALT: () => {
this.consumeTokenType(tokens.LeftParen);
this.parser.MANY_SEP({
SEP: tokens.Comma,
DEF: () => this.subrule(this.pattern),
});
this.consumeTokenType(tokens.RightParen);
},
},
// Typed pattern: pattern : Type
{
ALT: () => {
this.subrule(this.pattern);
this.consumeTokenType(tokens.Colon);
this.subrule(this.type);
},
GATE: () => {
// Complex lookahead for typed patterns
let i = 1;
let parenDepth = 0;
while (i < 20) {
const token = this.parser.LA(i);
if (!token) return false;
if (token.tokenType === tokens.LeftParen) parenDepth++;
if (token.tokenType === tokens.RightParen) parenDepth--;
if (parenDepth === 0 && token.tokenType === tokens.Colon) {
return true;
}
if (
parenDepth === 0 &&
(token.tokenType === tokens.Arrow ||
token.tokenType === tokens.Equals ||
token.tokenType === tokens.If)
) {
return false;
}
i++;
}
return false;
},
},
// Alternative pattern: p1 | p2 | ...
{
ALT: () => {
this.subrule(this.pattern);
this.parser.MANY(() => {
this.consumeTokenType(tokens.BitwiseOr);
this.subrule(this.pattern);
});
},
GATE: () => {
// Look for | in patterns
let i = 1;
let parenDepth = 0;
while (i < 20) {
const token = this.parser.LA(i);
if (!token) return false;
if (token.tokenType === tokens.LeftParen) parenDepth++;
if (token.tokenType === tokens.RightParen) parenDepth--;
if (parenDepth === 0 && token.tokenType === tokens.BitwiseOr) {
return true;
}
if (
parenDepth === 0 &&
(token.tokenType === tokens.Arrow ||
token.tokenType === tokens.Equals)
) {
return false;
}
i++;
}
return false;
},
},
]);
});
// Case clause (used in match expressions and partial functions)
caseClause = this.parser.RULE("caseClause", () => {
this.consumeTokenType(tokens.Case);
this.subrule(this.pattern);
// Optional guard
this.parser.OPTION(() => {
this.consumeTokenType(tokens.If);
this.subrule(this.expression);
});
this.consumeTokenType(tokens.Arrow);
// Case body - can be expression or block
this.parser.OR([
// Block of statements
{
ALT: () => {
this.parser.MANY(() => {
this.subrule(this.expression);
this.parser.OPTION(() => this.consumeTokenType(tokens.Semicolon));
});
},
GATE: () => {
// If next token is 'case' or '}', this is the end
const la1 = this.parser.LA(1);
return (
la1?.tokenType !== tokens.Case &&
la1?.tokenType !== tokens.RightBrace
);
},
},
// Empty case (rare but valid)
{ ALT: () => {} },
]);
});
// Generator (used in for comprehensions)
generator = this.parser.RULE("generator", () => {
this.parser.OR([
// Pattern generator: pattern <- expression
{
ALT: () => {
this.subrule(this.pattern);
this.consumeTokenType(tokens.LeftArrow);
this.subrule(this.expression);
},
},
// Value definition: pattern = expression
{
ALT: () => {
this.subrule(this.pattern);
this.consumeTokenType(tokens.Equals);
this.subrule(this.expression);
},
},
// Guard: if expression
{
ALT: () => {
this.consumeTokenType(tokens.If);
this.subrule(this.expression);
},
},
]);
});
// Extractor pattern (for advanced pattern matching)
extractorPattern = this.parser.RULE("extractorPattern", () => {
this.subrule(this.qualifiedIdentifier);
this.consumeTokenType(tokens.LeftParen);
this.parser.MANY_SEP({
SEP: tokens.Comma,
DEF: () => {
this.parser.OR([
// Regular pattern
{ ALT: () => this.subrule(this.pattern) },
// Sequence pattern: _*
{
ALT: () => {
this.consumeTokenType(tokens.Underscore);
this.consumeTokenType(tokens.Star);
},
},
]);
},
});
this.consumeTokenType(tokens.RightParen);
});
// Infix pattern (for pattern matching with infix operators)
infixPattern = this.parser.RULE("infixPattern", () => {
this.subrule(this.pattern);
this.consumeTokenType(tokens.Identifier);
this.subrule(this.pattern);
});
// XML pattern (if XML support is needed)
xmlPattern = this.parser.RULE("xmlPattern", () => {
// Placeholder for XML patterns
// This would require XML-specific tokens
this.consumeTokenType(tokens.StringLiteral);
});
}

Some files were not shown because too many files have changed in this diff Show More