diff --git a/packages/java-parser/package.json b/packages/java-parser/package.json index 44d610ec..f241e593 100644 --- a/packages/java-parser/package.json +++ b/packages/java-parser/package.json @@ -6,7 +6,7 @@ "repository": "https://github.com/jhipster/prettier-java/tree/master/packages/java-parser", "license": "Apache-2.0", "dependencies": { - "chevrotain": "4.7.0", + "chevrotain": "6.5.0", "lodash": "4.17.15" }, "scripts": { diff --git a/packages/java-parser/src/parser.js b/packages/java-parser/src/parser.js index 93c757fd..c8f6dec5 100644 --- a/packages/java-parser/src/parser.js +++ b/packages/java-parser/src/parser.js @@ -41,97 +41,6 @@ class JavaParser extends Parser { // TODO: performance: maxLookahead = 1 may be faster, but could we refactor the grammar to it? // and more importantly, do we want to? maxLookahead: 2, - // ambiguities resolved by backtracking - ignoredIssues: { - binaryExpression: { - OR: true - }, - lambdaParameterType: { - OR: true - }, - annotation: { - OR: true - }, - localVariableType: { - OR: true - }, - annotationTypeMemberDeclaration: { - OR: true - }, - typeDeclaration: { - OR: true - }, - typeArgument: { - OR: true - }, - type: { - OR: true - }, - referenceType: { - OR: true - }, - compilationUnit: { - OR: true - }, - classBodyDeclaration: { - OR: true - }, - classMemberDeclaration: { - OR: true - }, - unannReferenceType: { - OR: true - }, - formalParameter: { - OR: true - }, - interfaceMemberDeclaration: { - OR: true - }, - blockStatement: { - OR: true - }, - forStatement: { - OR: true - }, - newExpression: { - OR: true - }, - arrayCreationExpression: { - OR: true, - OR2: true - }, - expression: { - OR: true - }, - lambdaParameterList: { - OR: true - }, - lambdaParameter: { - OR: true - }, - primaryPrefix: { - OR: true - }, - castExpression: { - OR: true - }, - referenceTypeCastExpression: { - OR: true - }, - elementValue: { - OR: true - }, - resource: { - OR: true - }, - forInit: { - OR: true - }, - interfaceDeclaration: { - OR: true - } - }, nodeLocationTracking: "full" }); @@ -159,7 +68,11 @@ class JavaParser extends Parser { blocksStatements.defineRules.call(this, $, t); expressions.defineRules.call(this, $, t); + this.firstForUnaryExpressionNotPlusMinus = []; this.performSelfAnalysis(); + this.firstForUnaryExpressionNotPlusMinus = expressions.computeFirstForUnaryExpressionNotPlusMinus.call( + this + ); } // hack to turn off CST building side effects during backtracking @@ -172,23 +85,25 @@ class JavaParser extends Parser { } BACKTRACK_LOOKAHEAD(production, errValue = false) { - this.isBackTrackingStack.push(1); - // TODO: "saveRecogState" does not handle the occurrence stack - const orgState = this.saveRecogState(); - try { - // hack to enable outputting none CST values from grammar rules. - this.outputCst = false; - return production.call(this); - } catch (e) { - if (isRecognitionException(e)) { - return errValue; + return this.ACTION(() => { + this.isBackTrackingStack.push(1); + // TODO: "saveRecogState" does not handle the occurrence stack + const orgState = this.saveRecogState(); + try { + // hack to enable outputting none CST values from grammar rules. + this.outputCst = false; + return production.call(this); + } catch (e) { + if (isRecognitionException(e)) { + return errValue; + } + throw e; + } finally { + this.outputCst = true; + this.reloadRecogState(orgState); + this.isBackTrackingStack.pop(); } - throw e; - } finally { - this.outputCst = true; - this.reloadRecogState(orgState); - this.isBackTrackingStack.pop(); - } + }); } setIgnoredComments(comments) { diff --git a/packages/java-parser/src/productions/blocks-and-statements.js b/packages/java-parser/src/productions/blocks-and-statements.js index 6d775da0..6fbfdc0a 100644 --- a/packages/java-parser/src/productions/blocks-and-statements.js +++ b/packages/java-parser/src/productions/blocks-and-statements.js @@ -28,12 +28,16 @@ function defineRules($, t) { const isLocalVariableDeclaration = this.BACKTRACK_LOOKAHEAD( $.isLocalVariableDeclaration ); + + const isClassDeclaration = this.BACKTRACK_LOOKAHEAD($.isClassDeclaration); + $.OR([ { GATE: () => isLocalVariableDeclaration, ALT: () => $.SUBRULE($.localVariableDeclarationStatement) }, { + GATE: () => isClassDeclaration, ALT: () => $.SUBRULE($.classDeclaration) }, { ALT: () => $.SUBRULE($.statement) } @@ -57,10 +61,13 @@ function defineRules($, t) { // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-LocalVariableType $.RULE("localVariableType", () => { - $.OR([ - { ALT: () => $.SUBRULE($.unannType) }, - { ALT: () => $.CONSUME(t.Var) } - ]); + $.OR({ + DEF: [ + { ALT: () => $.SUBRULE($.unannType) }, + { ALT: () => $.CONSUME(t.Var) } + ], + IGNORE_AMBIGUITIES: true + }); }); // https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-Statement diff --git a/packages/java-parser/src/productions/classes.js b/packages/java-parser/src/productions/classes.js index 0b2a5662..3b2d87ba 100644 --- a/packages/java-parser/src/productions/classes.js +++ b/packages/java-parser/src/productions/classes.js @@ -653,13 +653,15 @@ function defineRules($, t) { }); $.RULE("isClassDeclaration", () => { + let isEmptyTypeDeclaration = false; + if ( $.OPTION(() => { $.CONSUME(t.Semicolon); }) ) { // an empty "TypeDeclaration" - return false; + isEmptyTypeDeclaration = true; } try { @@ -683,6 +685,10 @@ function defineRules($, t) { } } + if (isEmptyTypeDeclaration) { + return false; + } + const nextTokenType = this.LA(1).tokenType; return ( tokenMatcher(nextTokenType, t.Class) || diff --git a/packages/java-parser/src/productions/expressions.js b/packages/java-parser/src/productions/expressions.js index bbc40e7a..83b27e56 100644 --- a/packages/java-parser/src/productions/expressions.js +++ b/packages/java-parser/src/productions/expressions.js @@ -92,10 +92,13 @@ function defineRules($, t) { }); $.RULE("lambdaParameterType", () => { - $.OR([ - { ALT: () => $.SUBRULE($.unannType) }, - { ALT: () => $.CONSUME(t.Var) } - ]); + $.OR({ + DEF: [ + { ALT: () => $.SUBRULE($.unannType) }, + { ALT: () => $.CONSUME(t.Var) } + ], + IGNORE_AMBIGUITIES: true + }); }); $.RULE("lambdaBody", () => { @@ -119,59 +122,63 @@ function defineRules($, t) { $.RULE("binaryExpression", () => { $.SUBRULE($.unaryExpression); $.MANY(() => { - $.OR([ - { - ALT: () => { - $.CONSUME(t.Instanceof); - $.SUBRULE($.referenceType); - } - }, - { - ALT: () => { - $.CONSUME(t.AssignmentOperator); - $.SUBRULE2($.expression); - } - }, - // This is an example of why Java does not have a well designed grammar - // See: https://manas.tech/blog/2008/10/12/why-java-generics-dont-have-problems-with-right-shift-operator.html - // TODO: ensure the LT/GT sequences have no whitespace between each other. - { - // TODO: this is a bug in Chevrotain lookahead calculation. the "BinaryOperator" token can match "Less" or "Greater" - // as well, but because it is a **token Category** Chevrotain does not understand it need to looks two tokens ahead. - GATE: () => - tokenMatcher($.LA(2).tokenType, t.Less) || - tokenMatcher($.LA(2).tokenType, t.Greater), - ALT: () => { - $.OR2([ - { - GATE: () => $.LA(1).startOffset + 1 === $.LA(2).startOffset, - ALT: () => { - $.CONSUME(t.Less); - $.CONSUME2(t.Less); - } - }, - { - GATE: () => $.LA(1).startOffset + 1 === $.LA(2).startOffset, - ALT: () => { - $.CONSUME(t.Greater); - $.CONSUME2(t.Greater); - $.OPTION({ - GATE: () => $.LA(0).startOffset + 1 === $.LA(1).startOffset, - DEF: () => $.CONSUME3(t.Greater) - }); + $.OR({ + DEF: [ + { + ALT: () => { + $.CONSUME(t.Instanceof); + $.SUBRULE($.referenceType); + } + }, + { + ALT: () => { + $.CONSUME(t.AssignmentOperator); + $.SUBRULE2($.expression); + } + }, + // This is an example of why Java does not have a well designed grammar + // See: https://manas.tech/blog/2008/10/12/why-java-generics-dont-have-problems-with-right-shift-operator.html + // TODO: ensure the LT/GT sequences have no whitespace between each other. + { + // TODO: this is a bug in Chevrotain lookahead calculation. the "BinaryOperator" token can match "Less" or "Greater" + // as well, but because it is a **token Category** Chevrotain does not understand it need to looks two tokens ahead. + GATE: () => + tokenMatcher($.LA(2).tokenType, t.Less) || + tokenMatcher($.LA(2).tokenType, t.Greater), + ALT: () => { + $.OR2([ + { + GATE: () => $.LA(1).startOffset + 1 === $.LA(2).startOffset, + ALT: () => { + $.CONSUME(t.Less); + $.CONSUME2(t.Less); + } + }, + { + GATE: () => $.LA(1).startOffset + 1 === $.LA(2).startOffset, + ALT: () => { + $.CONSUME(t.Greater); + $.CONSUME2(t.Greater); + $.OPTION({ + GATE: () => + $.LA(0).startOffset + 1 === $.LA(1).startOffset, + DEF: () => $.CONSUME3(t.Greater) + }); + } } - } - ]); - $.SUBRULE2($.unaryExpression); - } - }, - { - ALT: () => { - $.CONSUME(t.BinaryOperator); - $.SUBRULE3($.unaryExpression); + ]); + $.SUBRULE2($.unaryExpression); + } + }, + { + ALT: () => { + $.CONSUME(t.BinaryOperator); + $.SUBRULE3($.unaryExpression); + } } - } - ]); + ], + IGNORE_AMBIGUITIES: true // the ambiguity between 1 and 4 options is resolved by the order (instanceOf is first) + }); }); }); @@ -604,21 +611,7 @@ function defineRules($, t) { return true; }); - let firstForUnaryExpressionNotPlusMinus = undefined; $.RULE("isReferenceTypeCastExpression", () => { - if (firstForUnaryExpressionNotPlusMinus === undefined) { - const firstUnaryExpressionNotPlusMinus = this.computeContentAssist( - "unaryExpressionNotPlusMinus", - [] - ); - const nextTokTypes = firstUnaryExpressionNotPlusMinus.map( - x => x.nextTokenType - ); - // uniq - firstForUnaryExpressionNotPlusMinus = nextTokTypes.filter( - (v, i, a) => a.indexOf(v) === i - ); - } $.CONSUME(t.LBrace); $.SUBRULE($.referenceType); $.MANY(() => { @@ -628,13 +621,14 @@ function defineRules($, t) { const firstTokTypeAfterRBrace = this.LA(1).tokenType; return ( - firstForUnaryExpressionNotPlusMinus.find(tokType => + this.firstForUnaryExpressionNotPlusMinus.find(tokType => tokenMatcher(firstTokTypeAfterRBrace, tokType) ) !== undefined ); }); $.RULE("isRefTypeInMethodRef", () => { + let result = undefined; $.SUBRULE($.typeArguments); // arrayType @@ -644,12 +638,12 @@ function defineRules($, t) { const firstTokTypeAfterTypeArgs = this.LA(1).tokenType; if (tokenMatcher(firstTokTypeAfterTypeArgs, t.ColonColon)) { - return true; + result = true; } // we must be at the end of a "referenceType" if "dims" were encountered // So there is not point to check farther else if (hasDims) { - return false; + result = false; } // in the middle of a "classReferenceType" @@ -658,11 +652,28 @@ function defineRules($, t) { $.SUBRULE($.classOrInterfaceType); }); + if (result !== undefined) { + return result; + } + const firstTokTypeAfterRefType = this.LA(1).tokenType; return tokenMatcher(firstTokTypeAfterRefType, t.ColonColon); }); } +function computeFirstForUnaryExpressionNotPlusMinus() { + const firstUnaryExpressionNotPlusMinus = this.computeContentAssist( + "unaryExpressionNotPlusMinus", + [] + ); + const nextTokTypes = firstUnaryExpressionNotPlusMinus.map( + x => x.nextTokenType + ); + // uniq + return nextTokTypes.filter((v, i, a) => a.indexOf(v) === i); +} + module.exports = { - defineRules + defineRules, + computeFirstForUnaryExpressionNotPlusMinus }; diff --git a/packages/java-parser/src/productions/interfaces.js b/packages/java-parser/src/productions/interfaces.js index 0c9133c9..8255ae28 100644 --- a/packages/java-parser/src/productions/interfaces.js +++ b/packages/java-parser/src/productions/interfaces.js @@ -71,6 +71,7 @@ function defineRules($, t) { const detectedType = this.BACKTRACK_LOOKAHEAD( $.identifyInterfaceBodyDeclarationType ); + $.OR([ { GATE: () => detectedType === InterfaceBodyTypes.constantDeclaration, @@ -170,6 +171,7 @@ function defineRules($, t) { const detectedType = this.BACKTRACK_LOOKAHEAD( $.identifyAnnotationBodyDeclarationType ); + $.OR([ { GATE: () => @@ -239,17 +241,22 @@ function defineRules($, t) { // https://docs.oracle.com/javase/specs/jls/se11/html/jls-9.html#jls-MarkerAnnotation $.OPTION(() => { $.CONSUME(t.LBrace); - $.OR([ - // normal annotation - https://docs.oracle.com/javase/specs/jls/se11/html/jls-9.html#jls-NormalAnnotation - { ALT: () => $.SUBRULE($.elementValuePairList) }, - // Single Element Annotation - https://docs.oracle.com/javase/specs/jls/se11/html/jls-9.html#jls-SingleElementAnnotation - { ALT: () => $.SUBRULE($.elementValue) }, - { - ALT: () => { - /* empty normal annotation contents */ + $.OR({ + DEF: [ + // normal annotation - https://docs.oracle.com/javase/specs/jls/se11/html/jls-9.html#jls-NormalAnnotation + { ALT: () => $.SUBRULE($.elementValuePairList) }, + // Single Element Annotation - https://docs.oracle.com/javase/specs/jls/se11/html/jls-9.html#jls-SingleElementAnnotation + { + ALT: () => $.SUBRULE($.elementValue) + }, + { + ALT: () => { + /* empty normal annotation contents */ + } } - } - ]); + ], + IGNORE_AMBIGUITIES: true + }); $.CONSUME(t.RBrace); }); }); @@ -274,6 +281,7 @@ function defineRules($, t) { const isSimpleElementValueAnnotation = this.BACKTRACK_LOOKAHEAD( $.isSimpleElementValueAnnotation ); + $.OR([ // Spec Deviation: "conditionalExpression" replaced with "expression" // Because we cannot differentiate between the two using fixed lookahead. diff --git a/packages/java-parser/src/productions/packages-and-modules.js b/packages/java-parser/src/productions/packages-and-modules.js index 99b61bd0..69118b26 100644 --- a/packages/java-parser/src/productions/packages-and-modules.js +++ b/packages/java-parser/src/productions/packages-and-modules.js @@ -6,6 +6,7 @@ function defineRules($, t) { $.RULE("compilationUnit", () => { // custom optimized backtracking lookahead logic const isModule = $.BACKTRACK_LOOKAHEAD($.isModuleCompilationUnit); + $.OR([ { GATE: () => isModule === false, @@ -98,6 +99,7 @@ function defineRules($, t) { $.RULE("typeDeclaration", () => { // TODO: consider extracting the prefix modifiers here to avoid backtracking const isClassDeclaration = this.BACKTRACK_LOOKAHEAD($.isClassDeclaration); + $.OR([ { GATE: () => isClassDeclaration, @@ -243,6 +245,7 @@ function defineRules($, t) { throw e; } } + const nextTokenType = this.LA(1).tokenType; return ( tokenMatcher(nextTokenType, t.Open) || diff --git a/packages/java-parser/src/productions/types-values-and-variables.js b/packages/java-parser/src/productions/types-values-and-variables.js index 3d793d9b..d9f747c6 100644 --- a/packages/java-parser/src/productions/types-values-and-variables.js +++ b/packages/java-parser/src/productions/types-values-and-variables.js @@ -56,24 +56,27 @@ function defineRules($, t) { }); // Spec Deviation: The array type "dims" suffix was extracted to this rule // to avoid backtracking for performance reasons. - $.OR([ - { - ALT: () => { - $.SUBRULE($.primitiveType); - $.SUBRULE($.dims); + $.OR({ + DEF: [ + { + ALT: () => { + $.SUBRULE($.primitiveType); + $.SUBRULE($.dims); + } + }, + { + // Spec Deviation: "typeVariable" alternative is missing because + // it is included in "classOrInterfaceType" + ALT: () => { + $.SUBRULE($.classOrInterfaceType); + $.OPTION(() => { + $.SUBRULE2($.dims); + }); + } } - }, - { - // Spec Deviation: "typeVariable" alternative is missing because - // it is included in "classOrInterfaceType" - ALT: () => { - $.SUBRULE($.classOrInterfaceType); - $.OPTION(() => { - $.SUBRULE2($.dims); - }); - } - } - ]); + ], + IGNORE_AMBIGUITIES: true // annotation prefix was extracted to remove ambiguities + }); }); // https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-ClassOrInterfaceType diff --git a/packages/prettier-plugin-java/src/printers/expressions.js b/packages/prettier-plugin-java/src/printers/expressions.js index 07c03b56..ee7004a8 100644 --- a/packages/prettier-plugin-java/src/printers/expressions.js +++ b/packages/prettier-plugin-java/src/printers/expressions.js @@ -170,7 +170,7 @@ class ExpressionsPrettierVisitor { for (let i = 0; i < subgroup.length; i++) { const token = subgroup[i]; const shiftOperator = isShiftOperator(subgroup, i); - if (token.tokenType.tokenName === "Instanceof") { + if (token.tokenType.name === "Instanceof") { currentSegment.push( rejectAndJoin(" ", [ctx.Instanceof[0], referenceType.shift()]) ); diff --git a/packages/prettier-plugin-java/src/printers/prettier-builder.js b/packages/prettier-plugin-java/src/printers/prettier-builder.js index 97baec11..e9bcc567 100644 --- a/packages/prettier-plugin-java/src/printers/prettier-builder.js +++ b/packages/prettier-plugin-java/src/printers/prettier-builder.js @@ -25,7 +25,7 @@ function getImageWithComments(token) { if (element.startLine !== token.startLine) { arr.push(concat(formatComment(element))); arr.push(hardLineWithoutBreakParent); - } else if (element.tokenType.tokenName === "LineComment") { + } else if (element.tokenType.name === "LineComment") { // Do not add extra space in case of empty statement const separator = token.image === "" ? "" : " "; arr.push( diff --git a/yarn.lock b/yarn.lock index e653f8a4..b1b52ee9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1517,10 +1517,10 @@ check-error@^1.0.2: resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= -chevrotain@4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/chevrotain/-/chevrotain-4.7.0.tgz#1a1acab4275cee8b1e208e6a10b1622c5b92b02e" - integrity sha512-FmXTi1hMN/6TT9NlrYP1kHHW6P7qpVzQD2vGrbsfI7wbFXXg2ANpIaN3pHPnVx/ZPHOnConE0TmyP6rguyzkZQ== +chevrotain@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/chevrotain/-/chevrotain-6.5.0.tgz#dcbef415516b0af80fd423cc0d96b28d3f11374e" + integrity sha512-BwqQ/AgmKJ8jcMEjaSnfMybnKMgGTrtDKowfTP3pX4jwVy0kNjRsT/AP6h+wC3+3NC+X8X15VWBnTCQlX+wQFg== dependencies: regexp-to-ast "0.4.0"