Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

prettier-plugin-apex

Package Overview
Dependencies
Maintainers
2
Versions
57
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

prettier-plugin-apex - npm Package Compare versions

Comparing version 1.0.0-beta.2 to 1.0.0-rc.1

src/http-client.js

4

bin/start-apex-server.js
#!/usr/bin/env node
const nailgunServer = require("../src/ng-server");
const httpServer = require("../src/http-server");
async function setup() {
await nailgunServer.start("localhost", 2113);
await httpServer.start("localhost", 2117);
}

@@ -7,0 +7,0 @@

#!/usr/bin/env node
const nailgunServer = require("../src/ng-server");
const httpServer = require("../src/http-server");
async function teardown() {
await nailgunServer.stop("localhost", 2113);
await httpServer.stop("localhost", 2117);
}

@@ -7,0 +7,0 @@

## Unreleased
## 1.0.0-rc.1
- CLI/Option change:
- Remove `apex-verify-ast` option. Please use `--debug-check` instead.
- Implement comments using Prettier's API.
- Add support for `prettier-ignore` directive ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/3)).
- Add support for `--require-pragma` and `--insert-pragma` CLI directives.
- Use internal HTTP server instead of Nailgun for built in parser.
- Fix unstable comments in between If/Else blocks.
- Fix unstable comments in between Try/Catch/Finally blocks.
- Fix unstable comments in WhereCompoundExpr.
- Fix unstable comments for NestedExpr.
- Fix unstable formatting for Method Declaration with no body.
- Fix unstable comments for Annotations.
- Fix unstable leading comments to ValueWhen and ElseWhen blocks.
- Throw errors when encountering unknown node types.
## 1.0.0-beta.2

@@ -4,0 +20,0 @@ - Fix long static method calls producing undeployable code ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/69)).

{
"name": "prettier-plugin-apex",
"version": "1.0.0-beta.2",
"version": "1.0.0-rc.1",
"description": "Salesforce Apex plugin for Prettier",

@@ -24,3 +24,4 @@ "main": "src/index.js",

"lint": "eslint \"{src,tests_config}/**/*.js\"",
"prettier": "prettier --write \"{src,tests_config}/**/*.js\""
"prettier": "prettier --write \"{src,tests_config}/**/*.js\"",
"debug-check": "prettier --apex-standalone-parser built-in --apex-standalone-port 2117 --debug-check --plugin=."
},

@@ -47,4 +48,4 @@ "keywords": [

"dependencies": {
"node-nailgun-client": "github:dangmai/node-nailgun-client#send_input",
"private": "^0.1.8",
"axios": "^0.19.0",
"jest-docblock": "^24.3.0",
"wait-on": "^3.2.0",

@@ -51,0 +52,0 @@ "yargs": "^13.2.1"

@@ -13,8 +13,5 @@ # Prettier Apex [![Build Status](https://travis-ci.org/dangmai/prettier-plugin-apex.svg)](https://travis-ci.org/dangmai/prettier-plugin-apex) [![npm](https://img.shields.io/npm/v/prettier-plugin-apex.svg)](https://www.npmjs.com/package/prettier-plugin-apex) [![codecov](https://codecov.io/gh/dangmai/prettier-plugin-apex/branch/master/graph/badge.svg)](https://codecov.io/gh/dangmai/prettier-plugin-apex) [![Join the chat at https://gitter.im/prettier-plugin-apex/community](https://badges.gitter.im/prettier-plugin-apex/community.svg)](https://gitter.im/prettier-plugin-apex/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

This project is actively being worked on, and has been tested on production code.
This project is production ready, and have been tested on multiple projects,
including a mix of open source/proprietary/Salesforce internal code bases.
* Formatting still needs work.
* Some defaults might be changed in the future, e.g. default number of spaces,
continuation indent, etc.
## Usage

@@ -68,14 +65,14 @@

so that you can revert any change if necessary.
You should also run Prettier with the `--apex-verify-ast` argument. For example:
You should also run Prettier with the `--debug-check` [argument](https://prettier.io/docs/en/cli.html#debug-check).
For example:
```bash
prettier --write "/path/to/project/**/*.{trigger,cls}" --apex-verify-ast
prettier --debug-check "/path/to/project/**/*.{trigger,cls}"
```
This will guarantee that the behavior of your code did not change because of
This will guarantee that the behavior of your code will not change because of
the format.
Note that this argument does degrade performance, so after the initial commit
feel free to stop using it in your day to day operation, provided that you only
format a small amount of code each time (for example, on a file save).
If there are no errors, you can run `prettier` with `--write` next.
If there are errors, please file a bug report so that they can be fixed.

@@ -98,2 +95,18 @@ #### Anonymous Apex

#### Ignoring lines
If there are lines in your Apex code that you do not want formatted by Prettier
(either because you don't agree with the formatting choice,
or there is a bug), you can instruct Prettier to ignore it by including the comment
`// prettier-ignore` or `/* prettier-ignore */` on the line before. For example:
```
// prettier-ignore
matrix(
1, 0, 0,
0, 1, 0,
0, 0, 1
)
```
### Configuration

@@ -119,2 +132,23 @@

## Editor integration
### VScode
The official plugin `prettier-vscode` doesn't support plugins out of the box yet, see [this issue](https://github.com/prettier/prettier-vscode/issues/395).
There are 2 workarounds to enable Apex support anyway:
- First way is to install the plugin into the plugin directory:
```bash
cd ~/.vscode/extensions/esbenp.prettier-vscode-1.8.1/
npm install prettier-plugin-apex
```
After restarting VScode the plugin should work as expected.
The downside is that you will need to do this every time the plugin gets updated.
- Second way is to use a patched version of `prettier-vscode` - there are 2 Pull Requests right now that add support for plugins: https://github.com/prettier/prettier-vscode/pull/817 and https://github.com/prettier/prettier-vscode/pull/757
Once either of them gets merged into the `master` branch, the VSCode plugin will support `prettier-plugin-apex`.
## Performance Tips/3rd party integration

@@ -127,3 +161,3 @@

In order to alleviate this issue,
we also have an optional [Nailgun](https://github.com/facebook/nailgun) server
we also have an optional HTTP server
that makes sure the start up is invoked exactly once.

@@ -130,0 +164,0 @@ This is especially useful if this library is integrated in a 3rd party application.

/* eslint no-param-reassign: 0, no-plusplus: 0, no-else-return: 0, consistent-return: 0 */
// We use the same algorithm to attach comments as recast
const assert = require("assert");
const prettier = require("prettier");
const { concat, lineSuffix, hardline } = prettier.doc.builders;
const { skipWhitespace } = prettier.util;
const childNodesCacheKey = require("private").makeUniqueKey();
const { concat, join, lineSuffix, hardline } = prettier.doc.builders;
const {
addDanglingComment,
addLeadingComment,
addTrailingComment,
hasNewlineInRange,
skipWhitespace,
} = prettier.util;
const constants = require("./constants");
const { isApexDocComment } = require("./util");
const apexTypes = constants.APEX_TYPES;
function getSortedChildNodes(node, resultArray) {
if (resultArray && node.loc) {
let i;
for (i = resultArray.length - 1; i >= 0; --i) {
if (resultArray[i].loc.endIndex - node.loc.startIndex <= 0) {
break;
}
}
resultArray.splice(i + 1, 0, node);
return;
} else if (node[childNodesCacheKey]) {
return node[childNodesCacheKey];
}
if (!Array.isArray(node) && typeof node !== "object") {
return;
}
const names = Object.keys(node).filter(key => key !== "apexComments");
if (!resultArray) {
Object.defineProperty(node, childNodesCacheKey, {
value: (resultArray = []),
enumerable: false,
});
}
let i;
for (i = 0; i < names.length; ++i) {
getSortedChildNodes(node[names[i]], resultArray);
}
return resultArray;
/**
* Print ApexDoc comment. This is straight from prettier handling of JSDoc
* @param comment the comment to print.
*/
function printApexDocComment(comment) {
const lines = comment.value.split("\n");
return concat([
join(
hardline,
lines.map(
(commentLine, index) =>
(index > 0 ? " " : "") +
(index < lines.length - 1
? commentLine.trim()
: commentLine.trimLeft()),
),
),
]);
}
function getRootNodeLocation(ast) {
// Some root node like TriggerDeclUnit has the `loc` property directly on it,
// while others has it in the `body` property. This function abstracts away
// that difference.
if (ast[apexTypes.PARSER_OUTPUT].unit.loc) {
return ast[apexTypes.PARSER_OUTPUT].unit.loc;
}
if (ast[apexTypes.PARSER_OUTPUT].unit.body.loc) {
return ast[apexTypes.PARSER_OUTPUT].unit.body.loc;
}
throw new Error(
"Cannot find the root node location. Please file a bug report with your code sample",
);
}
function decorateComment(node, comment, ast) {
// Special case: Comment is the first thing in the document,
// then "unit" node would be the followingNode to it.
if (comment.location.endIndex < getRootNodeLocation(ast).startIndex) {
comment.followingNode = ast[apexTypes.PARSER_OUTPUT].unit;
return;
}
// Handling the normal cases
const childNodes = getSortedChildNodes(node);
let left = 0;
let right = childNodes.length;
let precedingNode;
let followingNode;
while (left < right) {
const middle = (left + right) >> 1; // eslint-disable-line no-bitwise
const child = childNodes[middle];
if (
child.loc.startIndex <= comment.location.startIndex &&
child.loc.endIndex >= comment.location.endIndex
) {
// The comment is completely contained by this child node
comment.enclosingNode = child;
// Special case: if the child is a declaration type and has no members,
// we should go no deeper; otherwise the `name` node will mistakenly
// be decorated as the preceding node to this comment.
const declarationUnits = [
apexTypes.CLASS_DECLARATION,
apexTypes.TRIGGER_DECLARATION_UNIT,
apexTypes.ENUM_DECLARATION,
apexTypes.INTERFACE_DECLARATION,
];
if (
declarationUnits.includes(child["@class"]) &&
child.members.length === 0
) {
return;
}
// Standard case: recursively decorating the children nodes
decorateComment(child, comment, ast);
return; // Abandon the binary search at this level
}
if (comment.location.startIndex >= child.loc.endIndex) {
// This child node falls completely after the comment.
// Because we will never consider this node or any nodes before it again,
// this node must be the closest preceding node we have encountered so far
precedingNode = child;
left = middle + 1;
continue; // eslint-disable-line no-continue
}
if (comment.location.endIndex <= child.loc.startIndex) {
// This child node falls completely after the comment.
// Because we will never consider this node or any nodes after
// it again, this node must be the closest following node we
// have encountered so far.
followingNode = child;
right = middle;
continue; // eslint-disable-line no-continue
}
throw new Error("Comment location overlaps with node location");
}
if (precedingNode) {
comment.precedingNode = precedingNode;
}
if (followingNode) {
comment.followingNode = followingNode;
}
}
function addCommentHelper(node, comment) {
const comments = node.apexComments || (node.apexComments = []);
comments.push(comment);
}
function addLeadingComment(node, comment) {
comment.leading = true;
comment.trailing = false;
addCommentHelper(node, comment);
}
function addDanglingComment(node, comment) {
comment.leading = false;
comment.trailing = false;
addCommentHelper(node, comment);
}
function addTrailingComment(node, comment) {
comment.leading = false;
comment.trailing = true;
addCommentHelper(node, comment);
}
function breakTies(tiesToBreak, sourceCode) {
const tieCount = tiesToBreak.length;
if (tieCount === 0) {
return;
}
const pn = tiesToBreak[0].precedingNode;
const fn = tiesToBreak[0].followingNode;
let gapEndPos = fn.loc.startIndex;
// Iterate backwards through tiesToBreak, examining the gaps
// between the tied comments. In order to qualify as leading, a
// comment must be separated from fn by an unbroken series of
// whitespace-only gaps (or other comments).
let comment;
let indexOfFirstLeadingComment;
for (
indexOfFirstLeadingComment = tieCount;
indexOfFirstLeadingComment > 0;
indexOfFirstLeadingComment -= 1
) {
comment = tiesToBreak[indexOfFirstLeadingComment - 1];
assert.strictEqual(comment.precedingNode, pn);
assert.strictEqual(comment.followingNode, fn);
const gap = sourceCode.slice(comment.location.endIndex, gapEndPos);
if (/\S/.test(gap)) {
// The gap string contained something other than whitespace.
break;
}
gapEndPos = comment.location.startIndex;
}
comment = tiesToBreak[indexOfFirstLeadingComment];
while (
comment &&
indexOfFirstLeadingComment <= tieCount &&
// If the comment is indented more deeply than the node itself, and there's
// non-whitespace characters before it on the same line, reconsider it as
// trailing.
comment.location.column > fn.loc.column &&
!/\n(\s*)$/.test(sourceCode.slice(0, comment.location.startIndex))
) {
indexOfFirstLeadingComment += 1;
comment = tiesToBreak[indexOfFirstLeadingComment];
}
tiesToBreak.forEach((commentNode, i) => {
if (i < indexOfFirstLeadingComment) {
addTrailingComment(pn, commentNode);
} else {
addLeadingComment(fn, commentNode);
}
});
tiesToBreak.length = 0;
}
function attach(ast, sourceCode) {
const comments = ast[apexTypes.PARSER_OUTPUT].hiddenTokenMap
.map(item => item[1])
.filter(
item =>
item["@class"] === apexTypes.INLINE_COMMENT ||
item["@class"] === apexTypes.BLOCK_COMMENT,
);
const tiesToBreak = [];
comments.forEach(comment => {
decorateComment(ast[apexTypes.PARSER_OUTPUT], comment, ast);
const pn = comment.precedingNode;
const en = comment.enclosingNode;
const fn = comment.followingNode;
if (pn && fn) {
const tieCount = tiesToBreak.length;
if (tieCount > 0) {
const lastTie = tiesToBreak[tieCount - 1];
assert.strictEqual(
lastTie.precedingNode === comment.precedingNode,
lastTie.followingNode === comment.followingNode,
);
if (lastTie.followingNode !== comment.followingNode) {
breakTies(tiesToBreak, sourceCode);
}
}
tiesToBreak.push(comment);
} else if (pn) {
if (en && en["@class"] === apexTypes.BLOCK_STATEMENT && !fn) {
// Special case: this is a trailing comment in a block statement
breakTies(tiesToBreak, sourceCode);
// Our algorithm for attaching comment generally attaches the comment
// to the last node, however we want to attach this comment to the last
// statement node instead.
addTrailingComment(en.stmnts[en.stmnts.length - 1], comment);
} else {
// No contest: we have a trailing comment.
breakTies(tiesToBreak, sourceCode);
addTrailingComment(pn, comment);
}
} else if (fn) {
// No contest: we have a leading comment.
breakTies(tiesToBreak, sourceCode);
addLeadingComment(fn, comment);
} else if (en) {
// The enclosing node has no child nodes at all, so what we
// have here is a dangling comment, e.g. [/* crickets */].
breakTies(tiesToBreak, sourceCode);
addDanglingComment(en, comment);
} else {
throw new Error("AST contains no nodes at all?");
}
});
breakTies(tiesToBreak, sourceCode);
comments.forEach(comment => {
comment.printed = false;
// These node references were useful for breaking ties, but we
// don't need them anymore, and they create cycles in the AST that
// may lead to infinite recursion if we don't delete them here.
delete comment.precedingNode;
delete comment.enclosingNode;
delete comment.followingNode;
});
}
function printLeadingComment(commentPath, options, print) {
const comment = commentPath.getValue();
const parts = [print(commentPath)];
if (comment.trailing) {
// When we print trailing comments as leading comments, we don't
// want to bring any trailing spaces along.
parts.push(hardline);
function printComment(path) {
// This handles both Inline and Block Comments.
// We don't just pass through the value because unlike other string literals,
// this should not be escaped
const comment = path.getValue();
let result;
const node = path.getValue();
if (isApexDocComment(node)) {
result = printApexDocComment(node);
} else {
const trailingWhitespace = options.originalText.slice(
comment.location.endIndex,
skipWhitespace(options.originalText, comment.location.endIndex),
);
const numberOfNewLines = (trailingWhitespace.match(/\n/g) || []).length;
if (trailingWhitespace.length > 0 && numberOfNewLines === 0) {
// If trailing space exists, we will add at most one space to replace it.
parts.push(" ");
} else if (numberOfNewLines > 0) {
// If the trailing space contains newlines, then replace it
// with at most 2 new lines
const numberOfNewLinesToInsert = Math.min(numberOfNewLines, 2);
parts.push(...Array(numberOfNewLinesToInsert).fill(hardline));
}
result = node.value;
}
return concat(parts);
}
function printTrailingComment(commentPath, options, print) {
const sourceCode = options.originalText;
const comment = commentPath.getValue(commentPath);
const parentNode = commentPath.getParentNode();
const loc = comment.location;
const parts = [];
const fromPos =
skipWhitespace(sourceCode, loc.startIndex - 1, {
backwards: true,
}) + 1;
const leadingSpace = sourceCode.slice(fromPos, loc.startIndex);
const numberOfNewLines = (leadingSpace.match(/\n/g) || []).length;
if (numberOfNewLines > 0) {
// If the leading space contains newlines, then add at most 2 new lines
const numberOfNewLinesToInsert = Math.min(numberOfNewLines, 2);
parts.push(...Array(numberOfNewLinesToInsert).fill(hardline));
if (comment.trailingEmptyLine) {
result = concat([result, hardline]);
}
// When we print trailing inline comments, we have to make sure that nothing
// else is printed after it (e.g. a semicolon), so we'll use lineSuffix
// from prettier to buffer the output
if (comment["@class"] === apexTypes.INLINE_COMMENT) {
if (
(leadingSpace.length > 0 && numberOfNewLines === 0) ||
parentNode["@class"] === apexTypes.LOCATION_IDENTIFIER
) {
parts.push(lineSuffix(concat([" ", print(commentPath)])));
} else {
parts.push(lineSuffix(print(commentPath)));
}
} else {
// Handling block comment, which does not need lineSuffix
if (leadingSpace.length > 0 && numberOfNewLines === 0) {
// If the leading space contains no newlines, then we add at most 1 space
parts.push(" ");
}
parts.push(print(commentPath));
}
return concat(parts);
comment.printed = true;
return result;
}
function printDanglingComment(commentPath, options, print) {
function printDanglingComment(commentPath, options) {
const sourceCode = options.originalText;

@@ -386,87 +79,323 @@ const comment = commentPath.getValue(commentPath);

if (comment["@class"] === apexTypes.INLINE_COMMENT) {
parts.push(lineSuffix(print(commentPath)));
parts.push(lineSuffix(printComment(commentPath)));
} else {
parts.push(print(commentPath));
parts.push(printComment(commentPath));
}
comment.printed = true;
return concat(parts);
}
function allowTrailingComments(apexClass) {
let trailingCommentsAllowed = constants.ALLOW_TRAILING_COMMENT.includes(
apexClass,
/**
* This is called by Prettier's comment handling code, in order for Prettier
* to tell if this is a node to which a comment can be attached.
*
* @param node The current node
* @returns {boolean} whether a comment can be attached to this node or not.
*/
function canAttachComment(node) {
return (
node.loc &&
node["@class"] &&
node["@class"] !== apexTypes.INLINE_COMMENT &&
node["@class"] !== apexTypes.BLOCK_COMMENT
);
const separatorIndex = apexClass.indexOf("$");
if (separatorIndex !== -1) {
const parentClass = apexClass.substring(0, separatorIndex);
trailingCommentsAllowed =
trailingCommentsAllowed || parentClass === apexTypes.STATEMENT;
}
return trailingCommentsAllowed;
}
function printComments(path, options, print) {
const value = path.getValue();
const innerLines = print(path);
const comments = value ? value.apexComments : null;
/**
* This is called by Prettier's comment handling code, in order to find out
* if this is a block comment.
*
* @param comment The current comment node.
* @returns {boolean} whether it is a block comment.
*/
function isBlockComment(comment) {
return comment["@class"] === apexTypes.BLOCK_COMMENT;
}
if (!comments || comments.filter(comment => !comment.printed).length === 0) {
return innerLines;
}
/**
* This is called by Prettier's comment handling code.
* We can use this to tell Prettier that we will print comments manually on
* certain nodes.
* @returns {boolean} whether or not we will print the comment on this node manually.
*/
function willPrintOwnComments(path) {
const node = path.getValue();
return !node || !node["@class"] || node["@class"] === apexTypes.ANNOTATION;
}
const leadingParts = [];
const trailingParts = [innerLines];
function getTrailingComments(node) {
return node.comments.filter(comment => comment.trailing);
}
path.each(commentPath => {
const comment = commentPath.getValue();
const { leading, trailing } = comment;
function handleDanglingComment(comment) {
const { enclosingNode } = comment;
if (
enclosingNode &&
constants.ALLOW_DANGLING_COMMENTS.indexOf(enclosingNode["@class"]) !== -1 &&
((enclosingNode.stmnts && enclosingNode.stmnts.length === 0) ||
(enclosingNode.members && enclosingNode.members.length === 0))
) {
addDanglingComment(enclosingNode, comment);
return true;
}
return false;
}
/**
* Brings the comments between if-else blocks into the trailing if/else block.
* For example, formating the next block:
* ```
* if (true) {
* }
* // Comment
* else {
* }
* ```
*
* Into:
*
* ```
* if (true) {
* } else {
* // Comment
* }
* ```
*/
function handleInBetweenConditionalComment(comment, sourceCode) {
const { enclosingNode, precedingNode, followingNode } = comment;
if (
enclosingNode &&
precedingNode &&
followingNode &&
enclosingNode["@class"] === apexTypes.IF_ELSE_BLOCK &&
precedingNode["@class"] === apexTypes.IF_BLOCK &&
(followingNode["@class"] === apexTypes.IF_BLOCK ||
followingNode["@class"] === apexTypes.ELSE_BLOCK)
) {
if (
leading ||
(trailing &&
!(
allowTrailingComments(value["@class"]) ||
comment["@class"] === apexTypes.BLOCK_COMMENT
))
precedingNode.stmnt["@class"] !== apexTypes.BLOCK_STATEMENT &&
precedingNode.stmnt.loc.endIndex === precedingNode.loc.endIndex &&
!hasNewlineInRange(
sourceCode,
precedingNode.stmnt.loc.endIndex,
comment.location.startIndex,
)
) {
leadingParts.push(printLeadingComment(commentPath, options, print));
} else if (trailing) {
trailingParts.push(printTrailingComment(commentPath, options, print));
} else if (!leading && !trailing) {
// Dangling comments
// Note: in this statement `Integer a = 1 /* Comment */;`
// the comment is considered dangling, since jorje considers the literal
// number 1 node to end after the comment
trailingParts.push(printTrailingComment(commentPath, options, print));
// The following code can be handled normally without us intervening,
// since the comment node should really be trailing to the expression
// if (true)
// System.debug('Hello') // Comment
// else {
// }
return false;
}
if (followingNode.stmnt["@class"] === apexTypes.BLOCK_STATEMENT) {
if (followingNode.stmnt.stmnts.length > 0) {
addLeadingComment(followingNode.stmnt.stmnts[0], comment);
} else {
addDanglingComment(followingNode.stmnt, comment);
}
} else {
throw new Error(
"Comment is not printed because we cannot determine its property. Please submit a bug report with your code sample",
);
addLeadingComment(followingNode.stmnt, comment);
}
}, "apexComments");
return true;
}
return false;
}
// eslint-disable-next-line prefer-spread
leadingParts.push.apply(leadingParts, trailingParts);
return concat(leadingParts);
/**
* Brings the comments between try/catch/finally blocks into the following block.
* For example, formating the next block:
* ```
* try {
* }
* // Comment
* catch (Exception ex) {
* }
* ```
*
* Into:
*
* ```
* try {
* } catch (Exception ex) {
* // Comment
* }
* ```
*/
function handleInBetweenTryCatchFinallyComment(comment) {
const { enclosingNode, precedingNode, followingNode } = comment;
if (
!enclosingNode ||
!precedingNode ||
!followingNode ||
enclosingNode["@class"] !== apexTypes.TRY_CATCH_FINALLY_BLOCK ||
(precedingNode["@class"] !== apexTypes.BLOCK_STATEMENT &&
precedingNode["@class"] !== apexTypes.CATCH_BLOCK) ||
(followingNode["@class"] !== apexTypes.CATCH_BLOCK &&
followingNode["@class"] !== apexTypes.FINALLY_BLOCK)
) {
return false;
}
if (followingNode.stmnt.stmnts.length > 0) {
addLeadingComment(followingNode.stmnt.stmnts[0], comment);
} else {
addDanglingComment(followingNode.stmnt, comment);
}
return true;
}
/**
* Check if this comment is an ApexDoc-style comment.
* This code is straight from prettier JSDoc detection.
* @param comment the comment to check.
* Turn the leading comment to a WhereExpression inside a
* WhereCompoundExpression into a trailing comment to the previous WhereExpression.
* The reason is that a WhereExpression does not contain the location of
* the WhereCompoundOp (e.g. AND, OR), and without doing that, the following
* transformation occurs:
* ```
* SELECT Id
* FROM Contact
* WHERE
* Name = 'Name'
* AND
* // Comment
* Name = 'Another Name'
* ```
* Instead, this looks better:
* ```
* SELECT Id
* FROM Contact
* WHERE
* Name = 'Name'
* // Comment
* AND Name = 'Another Name'
* ```
*/
function isApexDocComment(comment) {
const lines = comment.value.split("\n");
function handleWhereExpression(comment, sourceCode) {
const { enclosingNode, precedingNode, followingNode } = comment;
if (
!enclosingNode ||
!precedingNode ||
!followingNode ||
!precedingNode["@class"] ||
!followingNode["@class"] ||
enclosingNode["@class"] !== apexTypes.WHERE_COMPOUND_EXPRESSION
) {
return false;
}
if (
hasNewlineInRange(
sourceCode,
precedingNode.loc.endIndex,
comment.location.startIndex,
)
) {
addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
/**
* This is called by Prettier's comment handling code, in order to handle
* comments that are on their own line.
*
* @param comment The comment node.
* @param sourceCode The entire source code.
* @returns {boolean} Whether we have manually attached this comment to some AST
* node. If `true` is returned, Prettier will no longer try to attach this
* comment based on its internal heuristic.
*/
function handleOwnLineComment(comment, sourceCode) {
return (
lines.length > 1 &&
lines
.slice(1, lines.length - 1)
.every(commentLine => commentLine.trim()[0] === "*")
handleDanglingComment(comment) ||
handleInBetweenConditionalComment(comment, sourceCode) ||
handleInBetweenTryCatchFinallyComment(comment) ||
handleWhereExpression(comment, sourceCode)
);
}
/**
* This is called by Prettier's comment handling code, in order to handle
* comments that have preceding text but no trailing text on a line.
*
* @param comment The comment node.
* @param sourceCode The entire source code.
* @returns {boolean} Whether we have manually attached this comment to some AST
* node. If `true` is returned, Prettier will no longer try to attach this
* comment based on its internal heuristic.
*/
function handleEndOfLineComment(comment, sourceCode) {
return (
handleDanglingComment(comment) ||
handleInBetweenConditionalComment(comment, sourceCode) ||
handleInBetweenTryCatchFinallyComment(comment) ||
handleWhereExpression(comment, sourceCode)
);
}
/**
* This is called by Prettier's comment handling code, in order to handle
* comments that have both preceding text and trailing text on a line.
*
* @param comment The comment node.
* @param sourceCode The entire source code.
* @returns {boolean} Whether we have manually attached this comment to some AST
* node. If `true` is returned, Prettier will no longer try to attach this
* comment based on its internal heuristic.
*/
function handleRemainingComment(comment, sourceCode) {
return (
handleInBetweenConditionalComment(comment, sourceCode) ||
handleInBetweenTryCatchFinallyComment(comment) ||
handleWhereExpression(comment, sourceCode)
);
}
/**
* This is called by Prettier's comment handling code, in order to find out
* if a node should be formatted or not.
* @param path The FastPath object.
* @returns {boolean} Whether the path should be formatted.
*/
function hasPrettierIgnore(path) {
const node = path.getValue();
return (
node &&
node.comments &&
node.comments.length > 0 &&
node.comments.filter(comment => {
let content;
if (!comment.leading) {
return false;
}
if (comment["@class"] === apexTypes.BLOCK_COMMENT) {
// For simplicity sake we only support this format
// /* prettier-ignore */
content = comment.value
.trim()
.substring(2, comment.value.length - 2)
.trim();
} else {
content = comment.value
.trim()
.substring(2)
.trim();
}
return content === "prettier-ignore";
}).length > 0
);
}
module.exports = {
attach,
canAttachComment,
getTrailingComments,
handleOwnLineComment,
handleEndOfLineComment,
handleRemainingComment,
hasPrettierIgnore,
isApexDocComment,
printComments,
isBlockComment,
printComment,
printDanglingComment,
willPrintOwnComments,
};

@@ -205,2 +205,9 @@ const constants = {

QUERY_LITERAL: "apex.jorje.data.soql.QueryLiteral",
QUERY_LITERAL_STRING: "apex.jorje.data.soql.QueryLiteral$QueryString",
QUERY_LITERAL_NULL: "apex.jorje.data.soql.QueryLiteral$QueryNull",
QUERY_LITERAL_TRUE: "apex.jorje.data.soql.QueryLiteral$QueryTrue",
QUERY_LITERAL_FALSE: "apex.jorje.data.soql.QueryLiteral$QueryFalse",
QUERY_LITERAL_NUMBER: "apex.jorje.data.soql.QueryLiteral$QueryNumber",
QUERY_LITERAL_DATE_FORMULA:
"apex.jorje.data.soql.QueryLiteral$QueryDateFormula",
QUERY_OPERATOR: "apex.jorje.data.soql.QueryOp",

@@ -382,16 +389,10 @@ SOQL_ORDER: "apex.jorje.data.soql.Order",

constants.ALLOW_TRAILING_COMMENT = [
constants.ALLOW_DANGLING_COMMENTS = [
constants.APEX_TYPES.TRIGGER_DECLARATION_UNIT,
constants.APEX_TYPES.CLASS_DECLARATION,
constants.APEX_TYPES.ENUM_DECLARATION,
constants.APEX_TYPES.INTERFACE_DECLARATION,
constants.APEX_TYPES.METHOD_DECLARATION,
constants.APEX_TYPES.ENUM_DECLARATION,
constants.APEX_TYPES.VARIABLE_DECLARATION,
constants.APEX_TYPES.LOCATION_IDENTIFIER,
constants.APEX_TYPES.LITERAL_EXPRESSION,
constants.APEX_TYPES.IF_BLOCK,
constants.APEX_TYPES.NAME_VALUE_PARAMETER,
constants.APEX_TYPES.WHERE_OPERATION_EXPRESSION,
constants.APEX_TYPES.WHERE_CLAUSE,
constants.APEX_TYPES.BLOCK_STATEMENT,
];
module.exports = constants;

@@ -0,2 +1,13 @@

const {
canAttachComment,
handleEndOfLineComment,
handleOwnLineComment,
handleRemainingComment,
hasPrettierIgnore,
isBlockComment,
printComment,
willPrintOwnComments,
} = require("./comments");
const parse = require("./parser");
const { hasPragma, insertPragma } = require("./pragma");
const print = require("./printer");

@@ -16,10 +27,9 @@ const { massageAstNode } = require("./util");

function locStart(node) {
if (node.loc) {
return node.loc;
}
return { line: -1, column: -1 };
const location = node.loc ? node.loc : node.location;
return location.startIndex;
}
function locEnd() {
return -1;
function locEnd(node) {
const location = node.loc ? node.loc : node.location;
return location.endIndex;
}

@@ -33,2 +43,3 @@

locEnd,
hasPragma,
preprocess: text => text.trim(),

@@ -42,2 +53,13 @@ },

massageAstNode,
hasPrettierIgnore,
insertPragma,
isBlockComment,
canAttachComment,
printComment,
willPrintOwnComments,
handleComments: {
ownLine: handleEndOfLineComment,
endOfLine: handleOwnLineComment,
remaining: handleRemainingComment,
},
},

@@ -58,3 +80,3 @@ };

value: "built-in",
description: "Use the built in standalone parser",
description: "Use the built in HTTP standalone parser",
},

@@ -68,13 +90,6 @@ ],

category: "Global",
default: 2113,
default: 2117,
description:
"The standalone server port to connect to. Only applicable if apexStandaloneParser is true",
"The standalone server port to connect to. Only applicable if apexStandaloneParser is true. Default to 2117.",
},
apexVerifyAst: {
type: "boolean",
category: "Global",
default: false,
description:
"Verify that the abstract syntax trees for the formatted code is the same as the unformatted code. This heavily degrades performance, but is recommended for initial runs on big code bases",
},
apexAnonymous: {

@@ -81,0 +96,0 @@ type: "boolean",

@@ -7,3 +7,2 @@ /* eslint no-param-reassign: 0 no-underscore-dangle: 0 */

const { spawnSync } = childProcess;
const attachComments = require("./comments").attach;
const constants = require("./constants");

@@ -14,2 +13,4 @@ const { findNextUncommentedCharacter } = require("./util");

const MAX_BUFFER = 8192 * 8192;
function parseTextWithSpawn(text, anonymous) {

@@ -28,2 +29,3 @@ let serializerBin = path.join(__dirname, "../vendor/apex-ast-serializer/bin");

input: text,
maxBuffer: MAX_BUFFER,
});

@@ -40,5 +42,13 @@

function parseTextWithNailgun(text, serverPort, anonymous) {
const ngClientLocation = path.join(__dirname, "ng-client.js");
const args = [ngClientLocation, "-a", "localhost", "-p", serverPort];
function parseTextWithHttp(text, serverPort, anonymous) {
const httpClientLocation = path.join(__dirname, "http-client.js");
const args = [
httpClientLocation,
"-a",
"localhost",
"-f",
"json",
"-p",
serverPort,
];
if (anonymous) {

@@ -49,2 +59,3 @@ args.push("-n");

input: text,
maxBuffer: MAX_BUFFER,
});

@@ -93,19 +104,22 @@

function handleInnerQueryLocation(location, sourceCode, commentNodes) {
const resultLocation = {};
resultLocation.startIndex = findNextUncommentedCharacter(
sourceCode,
"(",
location.startIndex,
commentNodes,
/* backwards */ true,
);
resultLocation.endIndex = findNextUncommentedCharacter(
sourceCode,
")",
location.startIndex,
commentNodes,
/* backwards */ false,
);
return resultLocation;
function handleNodeSurroundedByCharacters(startCharacter, endCharacter) {
return (location, sourceCode, commentNodes) => {
const resultLocation = {};
resultLocation.startIndex = findNextUncommentedCharacter(
sourceCode,
startCharacter,
location.startIndex,
commentNodes,
/* backwards */ true,
);
resultLocation.endIndex =
findNextUncommentedCharacter(
sourceCode,
endCharacter,
location.startIndex,
commentNodes,
/* backwards */ false,
) + 1;
return resultLocation;
};
}

@@ -117,9 +131,10 @@

resultLocation.startIndex = location.startIndex;
resultLocation.endIndex = findNextUncommentedCharacter(
sourceCode,
endCharacter,
location.endIndex,
commentNodes,
/* backwards */ false,
);
resultLocation.endIndex =
findNextUncommentedCharacter(
sourceCode,
endCharacter,
location.endIndex,
commentNodes,
/* backwards */ false,
) + 1;
return resultLocation;

@@ -136,2 +151,14 @@ };

function handleMethodDeclaration(location, sourceCode, commentNodes, node) {
// This is a method declaration with a body, so we can safely use the identity
// location.
if (node.stmnt.value) {
return location;
}
// This is a Method Declaration with no body, in which case we need to use the
// position of the closing parenthesis for the input parameters, e.g:
// void method();
return handleNodeEndedWithCharacter(")")(location, sourceCode, commentNodes);
}
// We need to generate the location for a node differently based on the node

@@ -141,2 +168,11 @@ // type. This object holds a String => Function mapping in order to do that.

const identityFunction = location => location;
// Sometimes we need to delete a location node. For example, a WhereCompoundOp
// location does not make sense since it can appear in multiple places:
// SELECT Id FROM Account
// WHERE Name = 'Name'
// AND Name = 'Other Name' // <- this AND node here
// AND Name = 'Yet Another Name' <- this AND node here
// If we keep those locations, a comment might be duplicated since it is
// attached to one WhereCompoundOp, and that operator is printed multiple times.
const removeFunction = () => null;
locationGenerationHandler[apexTypes.QUERY] = identityFunction;

@@ -150,3 +186,18 @@ locationGenerationHandler[apexTypes.VARIABLE_EXPRESSION] = identityFunction;

locationGenerationHandler[apexTypes.NAME_VALUE_PARAMETER] = identityFunction;
locationGenerationHandler[apexTypes.VARIABLE_DECLARATION] = identityFunction;
locationGenerationHandler[apexTypes.BINARY_EXPRESSION] = identityFunction;
locationGenerationHandler[apexTypes.BOOLEAN_EXPRESSION] = identityFunction;
locationGenerationHandler[apexTypes.ASSIGNMENT_EXPRESSION] = identityFunction;
locationGenerationHandler[apexTypes.FIELD_MEMBER] = identityFunction;
locationGenerationHandler[apexTypes.VALUE_WHEN] = identityFunction;
locationGenerationHandler[apexTypes.ELSE_WHEN] = identityFunction;
locationGenerationHandler[apexTypes.QUERY] = identityFunction;
locationGenerationHandler[apexTypes.WHERE_COMPOUND_OPERATOR] = removeFunction;
locationGenerationHandler[
apexTypes.VARIABLE_DECLARATION_STATEMENT
] = identityFunction;
locationGenerationHandler[
apexTypes.WHERE_COMPOUND_EXPRESSION
] = identityFunction;
locationGenerationHandler[
apexTypes.WHERE_OPERATION_EXPRESSION

@@ -156,3 +207,3 @@ ] = identityFunction;

apexTypes.SELECT_INNER_QUERY
] = handleInnerQueryLocation;
] = handleNodeSurroundedByCharacters("(", ")");
locationGenerationHandler[

@@ -162,2 +213,5 @@ apexTypes.ANONYMOUS_BLOCK_UNIT

locationGenerationHandler[
apexTypes.NESTED_EXPRESSION
] = handleNodeSurroundedByCharacters("(", ")");
locationGenerationHandler[
apexTypes.PROPERTY_MEMBER

@@ -169,11 +223,13 @@ ] = handleNodeEndedWithCharacter("}");

locationGenerationHandler[
apexTypes.VARIABLE_DECLARATION_STATEMENT
apexTypes.VARIABLE_DECLARATIONS
] = handleNodeEndedWithCharacter(";");
locationGenerationHandler[
apexTypes.FIELD_MEMBER
] = handleNodeEndedWithCharacter(";");
locationGenerationHandler[
apexTypes.NEW_KEY_VALUE
] = handleNodeEndedWithCharacter(")");
locationGenerationHandler[apexTypes.QUERY] = handleNodeEndedWithCharacter("]");
locationGenerationHandler[
apexTypes.METHOD_CALL_EXPRESSION
] = handleNodeEndedWithCharacter(")");
locationGenerationHandler[
apexTypes.METHOD_DECLARATION
] = handleMethodDeclaration;

@@ -212,9 +268,23 @@ /**

const apexClass = node["@class"];
if (apexClass && apexClass in locationGenerationHandler && currentLocation) {
node.loc = locationGenerationHandler[apexClass](
currentLocation,
sourceCode,
commentNodes,
);
let handlerFn;
if (apexClass) {
const separatorIndex = apexClass.indexOf("$");
if (separatorIndex !== -1) {
const parentClass = apexClass.substring(0, separatorIndex);
if (parentClass in locationGenerationHandler) {
handlerFn = locationGenerationHandler[parentClass];
}
}
if (apexClass in locationGenerationHandler) {
handlerFn = locationGenerationHandler[apexClass];
}
}
if (handlerFn && currentLocation) {
node.loc = handlerFn(currentLocation, sourceCode, commentNodes, node);
} else if (handlerFn && node.loc) {
node.loc = handlerFn(node.loc, sourceCode, commentNodes, node);
}
if (!node.loc) {
delete node.loc;
}
if (node.loc && currentLocation) {

@@ -378,3 +448,3 @@ if (node.loc.startIndex > currentLocation.startIndex) {

if (options.apexStandaloneParser === "built-in") {
serializedAst = parseTextWithNailgun(
serializedAst = parseTextWithHttp(
sourceCode,

@@ -411,3 +481,9 @@ options.apexStandalonePort,

generateExtraMetadata(ast, getEmptyLineLocations(sourceCode), true);
attachComments(ast, sourceCode);
ast.comments = ast[apexTypes.PARSER_OUTPUT].hiddenTokenMap
.map(token => token[1])
.filter(
node =>
node["@class"] === apexTypes.INLINE_COMMENT ||
node["@class"] === apexTypes.BLOCK_COMMENT,
);
}

@@ -414,0 +490,0 @@ return ast;

/* eslint no-param-reassign: 0 */
const { isApexDocComment } = require("./comments");
const constants = require("./constants");

@@ -15,2 +14,17 @@

/**
* Check if this comment is an ApexDoc-style comment.
* This code is straight from prettier JSDoc detection.
* @param comment the comment to check.
*/
function isApexDocComment(comment) {
const lines = comment.value.split("\n");
return (
lines.length > 1 &&
lines
.slice(1, lines.length - 1)
.every(commentLine => commentLine.trim()[0] === "*")
);
}
function checkIfParentIsDottedExpression(path) {

@@ -61,3 +75,3 @@ const node = path.getValue();

// prettier tries to do it so we are not going to bother either.
"apexComments",
"comments",
"$",

@@ -194,4 +208,5 @@ "leading",

getPrecedence,
isApexDocComment,
isBinaryish,
massageAstNode,
};

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc