diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ddd642f --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# use glob syntax. +syntax: glob +*.ser +*.class +*~ +*.bak +*.off +*.old +.DS_Store + +# lib'n misc +lib/ +file/ + +# eclipse conf file +.settings +.classpath +.project + + +# building +target/ +bin/ +test-output/ +out/ + +# Exported plugin for deployment +IntelliJBehave.zip + +# other scm +.svn +.CVS +.hg* + +# switch to regexp syntax. +# syntax: regexp +# ^\.pc/ + +# IntelliJ +*.iml +*.ipr +*.iws +.idea diff --git a/IntelliJBehave.iml b/IntelliJBehave.iml index f72ee21..634f32d 100644 --- a/IntelliJBehave.iml +++ b/IntelliJBehave.iml @@ -8,18 +8,33 @@ + + - + - + + + + + + + + + + + + + + diff --git a/META-INF/plugin.xml b/META-INF/plugin.xml index 90070a4..1c08b47 100644 --- a/META-INF/plugin.xml +++ b/META-INF/plugin.xml @@ -31,7 +31,7 @@
  • Run *.story files
  • ]]> - 1.2 + 1.3.SNAPSHOT Aman Kumar @@ -70,6 +70,8 @@ + + diff --git a/lib/jbehave-core-3.5.4.jar b/lib/jbehave-core-3.5.4.jar deleted file mode 100644 index e9bb049..0000000 Binary files a/lib/jbehave-core-3.5.4.jar and /dev/null differ diff --git a/lib/jbehave-core-3.6.jar b/lib/jbehave-core-3.6.jar new file mode 100644 index 0000000..ad796c4 Binary files /dev/null and b/lib/jbehave-core-3.6.jar differ diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..5a555c8 --- /dev/null +++ b/notes.md @@ -0,0 +1,144 @@ +http://confluence.jetbrains.net/display/IDEADEV/Developing+Custom+Language+Plugins+for+IntelliJ+IDEA#DevelopingCustomLanguagePluginsforIntelliJIDEA-ImplementingaParserandPSI + +Blue Forest theme: + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/github/kumaraman21/intellijbehave/codeInspector/UndefinedStepsInspection.java b/src/com/github/kumaraman21/intellijbehave/codeInspector/UndefinedStepsInspection.java index 3ab5316..22d65cf 100644 --- a/src/com/github/kumaraman21/intellijbehave/codeInspector/UndefinedStepsInspection.java +++ b/src/com/github/kumaraman21/intellijbehave/codeInspector/UndefinedStepsInspection.java @@ -15,65 +15,114 @@ */ package com.github.kumaraman21.intellijbehave.codeInspector; +import com.github.kumaraman21.intellijbehave.highlighter.StorySyntaxHighlighter; import com.github.kumaraman21.intellijbehave.parser.StepPsiElement; +import com.github.kumaraman21.intellijbehave.resolver.StepDefinitionAnnotation; +import com.github.kumaraman21.intellijbehave.utility.ParametrizedString; import com.intellij.codeInspection.BaseJavaLocalInspectionTool; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.codeInspection.ex.ProblemDescriptorImpl; +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; + import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; public class UndefinedStepsInspection extends BaseJavaLocalInspectionTool { - @Nls - @NotNull - @Override - public String getGroupDisplayName() { - return "JBehave"; - } - - @Nls - @NotNull - @Override - public String getDisplayName() { - return "Undefined step"; - } - - @NotNull - @Override - public String getShortName() { - return "UndefinedStep"; - } - - @Override - public String getStaticDescription() { - return super.getStaticDescription(); - } - - @Override - public boolean isEnabledByDefault() { - return true; - } - - @NotNull - @Override - public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { - return new PsiElementVisitor() { - - @Override - public void visitElement(PsiElement psiElement) { - super.visitElement(psiElement); - - if(! (psiElement instanceof StepPsiElement)) { - return; - } + @Nls + @NotNull + @Override + public String getGroupDisplayName() { + return "JBehave"; + } + + @Nls + @NotNull + @Override + public String getDisplayName() { + return "Undefined step"; + } + + @NotNull + @Override + public String getShortName() { + return "UndefinedStep"; + } + + @Override + public String getStaticDescription() { + return super.getStaticDescription(); + } + + @Override + public boolean isEnabledByDefault() { + return true; + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { + return new PsiElementVisitor() { - StepPsiElement stepPsiElement = (StepPsiElement) psiElement; - if(stepPsiElement.getReference().resolve() == null) { - holder.registerProblem(stepPsiElement, "Step #ref is not defined"); + @Override + public void visitElement(PsiElement psiElement) { + super.visitElement(psiElement); + + if (!(psiElement instanceof StepPsiElement)) { + return; + } + + StepPsiElement stepPsiElement = (StepPsiElement) psiElement; + StepDefinitionAnnotation annotationDef = stepPsiElement.getReference().stepDefinitionAnnotation(); + if (annotationDef == null) { + holder.registerProblem(stepPsiElement, "Step #ref is not defined"); + } + else { + highlightParameters(stepPsiElement, annotationDef, holder); + } + } + }; + } + + + private void highlightParameters(StepPsiElement stepPsiElement, + StepDefinitionAnnotation annotation, + ProblemsHolder holder) + { + String stepText = stepPsiElement.getStepText(); + String annotationText = annotation.getAnnotationText(); + ParametrizedString pString = new ParametrizedString(annotationText); + + int offset = stepPsiElement.getStepTextOffset(); + for (ParametrizedString.StringToken token : pString.tokenize(stepText)) { + int length = token.getValue().length(); + if (token.isIdentifier()) { + registerHiglighting(StorySyntaxHighlighter.TABLE_CELL, + stepPsiElement, + TextRange.from(offset, length), + holder); + } + offset += length; } - } - }; - } + } + + private static void registerHiglighting(TextAttributesKey attributesKey, + StepPsiElement step, + TextRange range, + ProblemsHolder holder) + { + final ProblemDescriptor descriptor = new ProblemDescriptorImpl( + step, step, "", LocalQuickFix.EMPTY_ARRAY, + ProblemHighlightType.INFORMATION, false, range, false, null, + holder.isOnTheFly()); + descriptor.setTextAttributes(attributesKey); + holder.registerProblem(descriptor); + } } diff --git a/src/com/github/kumaraman21/intellijbehave/completion/StoryCompletionContributor.java b/src/com/github/kumaraman21/intellijbehave/completion/StoryCompletionContributor.java new file mode 100644 index 0000000..059bc54 --- /dev/null +++ b/src/com/github/kumaraman21/intellijbehave/completion/StoryCompletionContributor.java @@ -0,0 +1,194 @@ +package com.github.kumaraman21.intellijbehave.completion; + +import static com.github.kumaraman21.intellijbehave.utility.ProjectUtils.getProjectFileIndex; + +import com.github.kumaraman21.intellijbehave.highlighter.StoryTokenType; +import com.github.kumaraman21.intellijbehave.parser.StepPsiElement; +import com.github.kumaraman21.intellijbehave.resolver.StepDefinitionAnnotation; +import com.github.kumaraman21.intellijbehave.resolver.StepDefinitionIterator; +import com.github.kumaraman21.intellijbehave.resolver.StepPsiReference; +import com.github.kumaraman21.intellijbehave.utility.LocalizedStorySupport; +import com.github.kumaraman21.intellijbehave.utility.ParametrizedString; +import com.intellij.codeInsight.completion.CompletionContributor; +import com.intellij.codeInsight.completion.CompletionParameters; +import com.intellij.codeInsight.completion.CompletionResultSet; +import com.intellij.codeInsight.completion.CompletionType; +import com.intellij.codeInsight.completion.CompletionUtil; +import com.intellij.codeInsight.completion.PrefixMatcher; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.lang.ASTNode; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiElement; +import com.intellij.util.Consumer; + +import org.jbehave.core.i18n.LocalizedKeywords; +import org.jbehave.core.steps.StepType; + +/** + * @author @aloyer + */ +public class StoryCompletionContributor extends CompletionContributor { + public StoryCompletionContributor() { + } + + @Override + public void fillCompletionVariants(CompletionParameters parameters, final CompletionResultSet _result) { + if (parameters.getCompletionType() == CompletionType.BASIC) { + String prefix = CompletionUtil.findReferenceOrAlphanumericPrefix(parameters); + CompletionResultSet result = _result.withPrefixMatcher(prefix); + + LocalizedKeywords keywords = lookupLocalizedKeywords(parameters); + Consumer consumer = newConsumer(_result); + + addAllKeywords(result.getPrefixMatcher(), consumer, keywords); + addAllSteps(parameters, + parameters.getInvocationCount() <= 1, + result.getPrefixMatcher(), + consumer, + keywords); + } + } + + private LocalizedKeywords lookupLocalizedKeywords(CompletionParameters parameters) { + String locale = "en"; + ASTNode localeNode = parameters.getOriginalFile().getNode().findChildByType(StoryTokenType.COMMENT_WITH_LOCALE); + if (localeNode != null) { + String localeFound = LocalizedStorySupport.checkForLanguageDefinition(localeNode.getText()); + if (localeFound != null) { + locale = localeFound; + } + } + return new LocalizedStorySupport().getKeywords(locale); + } + + private static Consumer newConsumer(final CompletionResultSet result) { + return new Consumer() { + @Override + public void consume(LookupElement element) { + result.addElement(element); + } + }; + } + + private static void addAllKeywords(PrefixMatcher prefixMatcher, + Consumer consumer, + LocalizedKeywords keywords) + { + addIfMatches(consumer, prefixMatcher, keywords.narrative()); + addIfMatches(consumer, prefixMatcher, keywords.asA()); + addIfMatches(consumer, prefixMatcher, keywords.inOrderTo()); + addIfMatches(consumer, prefixMatcher, keywords.iWantTo()); + // + addIfMatches(consumer, prefixMatcher, keywords.givenStories()); + addIfMatches(consumer, prefixMatcher, keywords.ignorable()); + addIfMatches(consumer, prefixMatcher, keywords.scenario()); + addIfMatches(consumer, prefixMatcher, keywords.examplesTable()); + // + addIfMatches(consumer, prefixMatcher, keywords.given()); + addIfMatches(consumer, prefixMatcher, keywords.when()); + addIfMatches(consumer, prefixMatcher, keywords.then()); + addIfMatches(consumer, prefixMatcher, keywords.and()); + } + + private static void addIfMatches(Consumer consumer, PrefixMatcher prefixMatchers, String input) { + if (prefixMatchers.prefixMatches(input)) { + consumer.consume(LookupElementBuilder.create(input)); + } + } + + private static void addAllSteps(CompletionParameters parameters, + boolean filterByScope, + PrefixMatcher prefixMatcher, + Consumer consumer, LocalizedKeywords keywords) + { + StepPsiElement stepPsiElement = getStepPsiElement(parameters); + if (stepPsiElement == null) { + return; + } + + StepType stepType = stepPsiElement.getStepType(); + String actualStepPrefix = stepPsiElement.getActualStepPrefix(); + // + String textBeforeCaret = CompletionUtil.findReferenceOrAlphanumericPrefix(parameters); + + // suggest only if at least the actualStepPrefix is complete + if (isStepTypeComplete(keywords, textBeforeCaret)) { + StepSuggester stepAnnotationFinder = new StepSuggester(prefixMatcher, + stepType, + actualStepPrefix, + textBeforeCaret, + consumer); + getProjectFileIndex().iterateContent(stepAnnotationFinder); + } + } + + private static boolean isStepTypeComplete(LocalizedKeywords keywords, String input) { + return input.startsWith(keywords.given()) + || input.startsWith(keywords.when()) + || input.startsWith(keywords.then()) + || input.startsWith(keywords.and()); + } + + private static StepPsiElement getStepPsiElement(CompletionParameters parameters) { + PsiElement position = parameters.getPosition(); + PsiElement positionParent = position.getParent(); + if (positionParent instanceof StepPsiElement) { + return (StepPsiElement) positionParent; + } + else if (position instanceof StepPsiReference) { + return (StepPsiElement) ((StepPsiReference) position).getElement(); + } + else if (position instanceof StepPsiElement) { + return (StepPsiElement) position; + } + else { + return null; + } + } + + private static class StepSuggester extends StepDefinitionIterator { + + private final PrefixMatcher prefixMatcher; + private final StepType stepType; + private final String actualStepPrefix; + private final String textBeforeCaret; + private final Consumer consumer; + + private StepSuggester(PrefixMatcher prefixMatcher, + StepType stepType, + String actualStepPrefix, + String textBeforeCaret, Consumer consumer) + { + super(null); + this.prefixMatcher = prefixMatcher; + this.stepType = stepType; + this.actualStepPrefix = actualStepPrefix; + this.textBeforeCaret = textBeforeCaret; + this.consumer = consumer; + } + + @Override + public boolean processStepDefinition(StepDefinitionAnnotation stepDefinitionAnnotation) { + StepType annotationStepType = stepDefinitionAnnotation.getStepType(); + if (annotationStepType != stepType) { + return true; + } + String annotationText = stepDefinitionAnnotation.getAnnotationText(); + String adjustedAnnotationText = actualStepPrefix + " " + annotationText; + + ParametrizedString pString = new ParametrizedString(adjustedAnnotationText); + String complete = pString.complete(textBeforeCaret); + if (StringUtil.isNotEmpty(complete)) { + PsiAnnotation matchingAnnotation = stepDefinitionAnnotation.getAnnotation(); + consumer.consume(LookupElementBuilder.create(matchingAnnotation, textBeforeCaret + complete)); + } + else if (prefixMatcher.prefixMatches(adjustedAnnotationText)) { + PsiAnnotation matchingAnnotation = stepDefinitionAnnotation.getAnnotation(); + consumer.consume(LookupElementBuilder.create(matchingAnnotation, adjustedAnnotationText)); + } + return true; + } + } +} diff --git a/src/com/github/kumaraman21/intellijbehave/highlighter/LexicalState.java b/src/com/github/kumaraman21/intellijbehave/highlighter/LexicalState.java new file mode 100644 index 0000000..e0840c5 --- /dev/null +++ b/src/com/github/kumaraman21/intellijbehave/highlighter/LexicalState.java @@ -0,0 +1,28 @@ +package com.github.kumaraman21.intellijbehave.highlighter; + +/** + * @author @aloyer + */ +public enum LexicalState { + YYINITIAL(_StoryLexer.YYINITIAL) + ,IN_DIRECTIVE(_StoryLexer.IN_DIRECTIVE) + ,IN_STORY(_StoryLexer.IN_STORY) + ,IN_SCENARIO(_StoryLexer.IN_SCENARIO) + ,IN_STEP(_StoryLexer.IN_STEP) + ,IN_META(_StoryLexer.IN_META) + ,IN_TABLE(_StoryLexer.IN_TABLE) + ,IN_EXAMPLES(_StoryLexer.IN_EXAMPLES) + ; + + private final int lexerId; + LexicalState(int lexerId) { + this.lexerId = lexerId; + } + + public static LexicalState fromLexer(int lexerId) { + for(LexicalState state : values()) + if(state.lexerId==lexerId) + return state; + throw new IllegalArgumentException("Unsupported lexer id: " + lexerId); + } +} diff --git a/src/com/github/kumaraman21/intellijbehave/highlighter/Story.flex b/src/com/github/kumaraman21/intellijbehave/highlighter/Story.flex index 91b82b0..41caddb 100644 --- a/src/com/github/kumaraman21/intellijbehave/highlighter/Story.flex +++ b/src/com/github/kumaraman21/intellijbehave/highlighter/Story.flex @@ -2,42 +2,174 @@ package com.github.kumaraman21.intellijbehave.highlighter; import com.intellij.lexer.FlexLexer; import com.intellij.psi.tree.IElementType; +import java.util.Stack; %% +%{ + private Stack yystates = new Stack () {{ push(YYINITIAL); }}; + public boolean trace = false; + + public void yystatePush(int yystate) { + if(trace) System.out.println(">>>> PUSH: " + LexicalState.fromLexer(yystate) + " [" + reverseAndMap(yystates) + "]"); + yybegin(yystate); + yystates.push(yystate); + } + + private String reverseAndMap(Stack yystates) { + StringBuilder builder = new StringBuilder(); + for(int i=yystates.size()-1; i>=0; i--) { + if(builder.length()>0) + builder.append(", "); + builder.append(LexicalState.fromLexer(yystates.get(i))); + } + return builder.toString(); + } + + public void yystatePopNPush(int yystate) { + yystatePopNPush(1, yystate); + } + + public void yystatePopNPush(int nb, int yystate) { + if(trace) System.out.println(">>>> POP'n PUSH : #" + nb + ", " + LexicalState.fromLexer(yystate) + " [" + reverseAndMap(yystates) + "]"); + for (int i = 0; i < nb; i++) { + yystatePop(); + } + yystatePush(yystate); + } + + public int yystatePop() { + int popped = yystates.pop(); + if(trace) System.out.println(">>>> POP : " + LexicalState.fromLexer(popped) + " [" + reverseAndMap(yystates) + "]"); + if(!yystates.isEmpty()) { + yybegin(yystates.peek()); + }// otherwise hopes a push will follow right after + return popped; + } + + public boolean checkAhead(char c) { + if (zzMarkedPos >= zzBuffer.length()) + return false; + return zzBuffer.charAt(zzMarkedPos) == c; + } +%} + %class _StoryLexer %implements FlexLexer %unicode %function advance %type IElementType -%eof{ return; -%eof} -CRLF= \n | \r | \r\n -WHITE_SPACE_CHAR=[\ \n\r\t\f] -TEXT_CHAR=[^\n\r] -COMMENT=("!--")[^\r\n]* +CRLF = \r|\n|\r\n +BlankChar = [ \t\f] +InputChar = [^\r\n] +WhiteSpace = {CRLF} | {BlankChar} +NonWhiteSpace = [^ \n\r\t\f] +TableCellChar = [^\r\n\|] +NonMetaKey = [^@\r\n] +%state IN_DIRECTIVE +%state IN_STORY %state IN_SCENARIO %state IN_STEP +%state IN_META +%state IN_TABLE +%state IN_EXAMPLES +%eof{ + return; +%eof} %% - {CRLF}+"Scenario:" {TEXT_CHAR}+ { yybegin(IN_SCENARIO); return StoryTokenType.SCENARIO_TEXT; } - {CRLF}+"Scenario:" {TEXT_CHAR}+ { yybegin(IN_SCENARIO); return StoryTokenType.SCENARIO_TEXT; } - {CRLF}+"Scenario:" {TEXT_CHAR}+ { yybegin(IN_SCENARIO); return StoryTokenType.SCENARIO_TEXT; } + { + ( "Scenario: " + | "Meta:" + | "Examples:" + | "Given " | "When " | "Then " | "And " + | "!--" + | "|" ) {InputChar}+ { yystatePush(IN_DIRECTIVE); yypushback(yytext().length()); } + {CRLF} { yystatePush(IN_STORY); yypushback(yytext().length()); } + {InputChar}+ { return StoryTokenType.STORY_DESCRIPTION; } +} + + { + "Scenario: " { yystatePopNPush(2, IN_SCENARIO); return StoryTokenType.SCENARIO_TYPE; } + "Meta:" { yystatePopNPush(2, IN_META); return StoryTokenType.META; } + "Examples:" { yystatePopNPush(2, IN_EXAMPLES); return StoryTokenType.EXAMPLE_TYPE; } + ("Given " | "When " | "Then " | "And ") { yystatePopNPush(2, IN_STEP); return StoryTokenType.STEP_TYPE; } + "!--" {InputChar}* { yystatePop(); return StoryTokenType.COMMENT; } + "|" { yystatePopNPush(1, IN_TABLE); return StoryTokenType.TABLE_DELIM; } + {WhiteSpace}+ { return StoryTokenType.WHITE_SPACE; } +} + + { + {CRLF} + ( "Scenario: " + | "Meta:" + | "Examples:" + | "Given " | "When " | "Then " | "And " + | "!--" + | "|" ) { yystatePush(IN_DIRECTIVE); yypushback(yytext().length()); } + {InputChar}+ { return StoryTokenType.STORY_DESCRIPTION; } + {CRLF} { return StoryTokenType.WHITE_SPACE; } +} + + + { + {CRLF} + ( "Scenario: " + | "Meta:" + | "Examples:" + | "Given " | "When " | "Then " | "And " + | "!--" + | "|" ) { yystatePush(IN_DIRECTIVE); yypushback(yytext().length()); } + {InputChar}+ { return StoryTokenType.SCENARIO_TEXT; } + {CRLF} { return StoryTokenType.WHITE_SPACE; } +} + + + { + "@" {NonWhiteSpace}* { return StoryTokenType.META_KEY; } + {CRLF} + ( "Scenario: " + | "Meta:" + | "Examples:" + | "Given " | "When " | "Then " | "And " + | "!--" + | "|" ) { yystatePush(IN_DIRECTIVE); yypushback(yytext().length()); } + {NonMetaKey}+ { return StoryTokenType.META_TEXT; } + {CRLF} { return StoryTokenType.WHITE_SPACE; } +} + + + { + {CRLF} + ( "Scenario: " + | "Meta:" + | "Examples:" + | "Given " | "When " | "Then " | "And " + | "!--" + | "|" ) { yystatePush(IN_DIRECTIVE); yypushback(yytext().length()); } + {InputChar}+ { return StoryTokenType.STEP_TEXT; } + {CRLF} { return StoryTokenType.WHITE_SPACE; } +} - {CRLF}+"Given" {WHITE_SPACE_CHAR} { yybegin(IN_STEP); return StoryTokenType.STEP_TYPE; } - {CRLF}+"When" {WHITE_SPACE_CHAR} { yybegin(IN_STEP); return StoryTokenType.STEP_TYPE; } - {CRLF}+"Then" {WHITE_SPACE_CHAR} { yybegin(IN_STEP); return StoryTokenType.STEP_TYPE; } - {CRLF}+"And" {WHITE_SPACE_CHAR} { yybegin(IN_STEP); return StoryTokenType.STEP_TYPE; } - {CRLF}+{WHITE_SPACE_CHAR}* "|" {TEXT_CHAR}* { yybegin(IN_SCENARIO); return StoryTokenType.TABLE_ROW; } + { + {CRLF} + ( "Scenario: " + | "Meta:" + | "Examples:" + | "Given " | "When " | "Then " | "And " + | "!--" + | "|" ) { yystatePush(IN_DIRECTIVE); yypushback(yytext().length()); } + {WhiteSpace} { return StoryTokenType.WHITE_SPACE; } +} - {COMMENT} { return StoryTokenType.COMMENT; } - .* { return StoryTokenType.STORY_DESCRIPTION; } - .* { return StoryTokenType.STORY_DESCRIPTION; } + { + {TableCellChar}+ { return StoryTokenType.TABLE_CELL; } + "|" { return StoryTokenType.TABLE_DELIM; } + {CRLF} { yystatePop(); yypushback(1); } +} - {TEXT_CHAR}* { yybegin(IN_SCENARIO); return StoryTokenType.STEP_TEXT; } +. { return StoryTokenType.BAD_CHARACTER; } -{WHITE_SPACE_CHAR}+ { return StoryTokenType.WHITE_SPACE; } -. { return StoryTokenType.BAD_CHARACTER; } diff --git a/src/com/github/kumaraman21/intellijbehave/highlighter/StoryColorsAndFontsPage.java b/src/com/github/kumaraman21/intellijbehave/highlighter/StoryColorsAndFontsPage.java new file mode 100644 index 0000000..28642d9 --- /dev/null +++ b/src/com/github/kumaraman21/intellijbehave/highlighter/StoryColorsAndFontsPage.java @@ -0,0 +1,93 @@ +package com.github.kumaraman21.intellijbehave.highlighter; + +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.fileTypes.SyntaxHighlighter; +import com.intellij.openapi.options.colors.AttributesDescriptor; +import com.intellij.openapi.options.colors.ColorDescriptor; +import com.intellij.openapi.options.colors.ColorSettingsPage; + +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import javax.swing.*; +import java.util.Map; + +/** + * @author @aloyer + */ +public class StoryColorsAndFontsPage implements ColorSettingsPage { + + @NotNull + public String getDisplayName() { + return "JBehave"; + } + + @Nullable + public Icon getIcon() { + return null;//IntelliJBehaveIcons.ICON_16x16; + } + + @NotNull + public AttributesDescriptor[] getAttributeDescriptors() { + return ATTRS; + } + + private static final AttributesDescriptor[] ATTRS = new AttributesDescriptor[]{ + new AttributesDescriptor("Story description", StorySyntaxHighlighter.STORY_DESCRIPTION),// + new AttributesDescriptor("Scenario keyword", StorySyntaxHighlighter.SCENARIO_TYPE),// + new AttributesDescriptor("Scenario text", StorySyntaxHighlighter.SCENARIO_TEXT),// + new AttributesDescriptor("Step keyword", StorySyntaxHighlighter.STEP_TYPE),// + new AttributesDescriptor("Step text", StorySyntaxHighlighter.STEP_TEXT), // + new AttributesDescriptor("Table delimiter", StorySyntaxHighlighter.TABLE_DELIM), + new AttributesDescriptor("Table cell", StorySyntaxHighlighter.TABLE_CELL),// + new AttributesDescriptor("Meta keyword", StorySyntaxHighlighter.META_TYPE),// + new AttributesDescriptor("Meta key", StorySyntaxHighlighter.META_KEY),// + new AttributesDescriptor("Meta text", StorySyntaxHighlighter.META_TEXT), // + new AttributesDescriptor("Line comment", StorySyntaxHighlighter.LINE_COMMENT),// + new AttributesDescriptor("Bad Character", StorySyntaxHighlighter.BAD_CHARACTER) + }; + + @NotNull + public ColorDescriptor[] getColorDescriptors() { + return new ColorDescriptor[0]; + } + + @NotNull + public SyntaxHighlighter getHighlighter() { + return new StorySyntaxHighlighter(); + } + + @NonNls + @NotNull + public String getDemoText() { + return "Narrative: \n" + // + "In order to play a game\n" + // + "As a player\n" + // + "I want to be able to create and manage my account\n" + // + "\n" + // + "Scenario: An unknown user cannot be logged\n" + // + "\n" + // + "Meta:\n" + // + "@author mccallum\n" + // + "@skip\n" + // + "\n" + // + "Given i am the user with nickname: \"weird\"\n" + // + "When i try to login using the password \"soweird\"\n" + // + "!-- TODO: Then i get an error message of type \"Wrong Credentials\"\n" + // + "\n" + // + "\n" + // + "Scenario: A known user cannot be logged using a wrong password\n" + // + "\n" + // + "Given the following existing users:\n" + // + "| nickname | password |\n" + // + "| Travis | PacMan |\n" + // + "Given i am the user with nickname: \"Travis\"\n" + // + "When i try to login using the password \"McCallum\"\n" + // + "Then i get an error message of type \"Wrong Credentials\""; + } + + @Nullable + public Map getAdditionalHighlightingTagToDescriptorMap() { + return null; + } +} diff --git a/src/com/github/kumaraman21/intellijbehave/highlighter/StoryLexer.java b/src/com/github/kumaraman21/intellijbehave/highlighter/StoryLexer.java index 9354d6e..740913f 100644 --- a/src/com/github/kumaraman21/intellijbehave/highlighter/StoryLexer.java +++ b/src/com/github/kumaraman21/intellijbehave/highlighter/StoryLexer.java @@ -23,4 +23,8 @@ public class StoryLexer extends FlexAdapter { public StoryLexer() { super(new _StoryLexer((Reader)null)); } + + public LexicalState lexerState() { + return LexicalState.fromLexer(getFlex().yystate()); + } } diff --git a/src/com/github/kumaraman21/intellijbehave/highlighter/StoryLexerFactory.java b/src/com/github/kumaraman21/intellijbehave/highlighter/StoryLexerFactory.java new file mode 100644 index 0000000..4b784d4 --- /dev/null +++ b/src/com/github/kumaraman21/intellijbehave/highlighter/StoryLexerFactory.java @@ -0,0 +1,17 @@ +package com.github.kumaraman21.intellijbehave.highlighter; + +import com.intellij.lexer.Lexer; + +/** + * @author @aloyer + */ +public class StoryLexerFactory { + public static final boolean USE_LOCALIZED = true; + + public Lexer createLexer() { + if(USE_LOCALIZED) + return new StoryLocalizedLexer(); + else + return new StoryLexer(); + } +} diff --git a/src/com/github/kumaraman21/intellijbehave/highlighter/StoryLocalizedLexer.java b/src/com/github/kumaraman21/intellijbehave/highlighter/StoryLocalizedLexer.java new file mode 100644 index 0000000..0a9f981 --- /dev/null +++ b/src/com/github/kumaraman21/intellijbehave/highlighter/StoryLocalizedLexer.java @@ -0,0 +1,473 @@ +package com.github.kumaraman21.intellijbehave.highlighter; + +import com.github.kumaraman21.intellijbehave.utility.CharTree; +import com.github.kumaraman21.intellijbehave.utility.JBKeyword; +import com.github.kumaraman21.intellijbehave.utility.LocalizedStorySupport; +import com.intellij.lexer.LexerBase; +import com.intellij.psi.tree.IElementType; +import com.intellij.util.ArrayUtil; + +import org.jbehave.core.i18n.LocalizedKeywords; + +/** + * @author @aloyer + */ +public class StoryLocalizedLexer extends LexerBase { + + /** + * lexical states + */ + public enum State { + YYINITIAL, + IN_DISPATCH, + IN_STORY, + IN_SCENARIO, + IN_STEP, + IN_TABLE, + IN_STEP_TABLE, + IN_META, + IN_EXAMPLES, IN_OTHER_TABLE + } + + private final LocalizedStorySupport kwSupport; + // + private LocalizedKeywords keywords; + private CharTree charTree; + private CharSequence buffer = ArrayUtil.EMPTY_CHAR_SEQUENCE; + //private int startOffset; + private int endOffset; + private State state = State.YYINITIAL; + private int position; + private IElementType tokenType; + private int currentTokenStart; + + public StoryLocalizedLexer() { + this(new LocalizedStorySupport()); + } + + public StoryLocalizedLexer(LocalizedStorySupport kwSupport) { + this.kwSupport = kwSupport; + changeLocale("en"); + } + + public void changeLocale(String locale) { + keywords = kwSupport.getKeywords(locale); + charTree = new CharTree('/', null); + for (JBKeyword kw : JBKeyword.values()) { + String asString = kw.asString(keywords); + charTree.push(asString, kw); + } + } + + @Override + public void start(CharSequence buffer, int startOffset, int endOffset, int initialState) { + this.buffer = buffer; + //this.startOffset = startOffset; + this.position = startOffset; + this.endOffset = endOffset; + this.state = State.values()[initialState]; + this.tokenType = null; + } + + @Override + public int getState() { + advanceIfRequired(); + return state.ordinal(); + } + + @Override + public IElementType getTokenType() { + advanceIfRequired(); + return tokenType; + } + + @Override + public int getTokenStart() { + advanceIfRequired(); + return currentTokenStart; + } + + @Override + public int getTokenEnd() { + advanceIfRequired(); + return position; + } + + @Override + public CharSequence getBufferSequence() { + return buffer; + } + + @Override + public int getBufferEnd() { + return endOffset; + } + + @Override + public void advance() { + advanceIfRequired(); + tokenType = null; + } + + private void advanceIfRequired() { + if (tokenType == null) { + locateToken(); + } + } + + public int getPosition() { + return position; + } + + private void locateToken() { + if (tokenType != null || position >= endOffset) { + return; + } + + this.currentTokenStart = position; + + if (consume(keywords.ignorable())) { + consume(INPUT_CHAR); + if (state == State.YYINITIAL) { + String locale = LocalizedStorySupport.checkForLanguageDefinition(tokenText()); + if(locale!=null) { + changeLocale(locale); + tokenType = StoryTokenType.COMMENT_WITH_LOCALE; + return; + } + } + tokenType = StoryTokenType.COMMENT; + return; + } + + switch (state) { + case YYINITIAL: + case IN_STORY: { + CharTree.Entry entry = charTree.lookup(buffer, position); + if (entry.hasValue()) { + tokenType = tokenType(entry.value); + position += entry.length; + return; + } + else if (consume(CRLF)) { + tokenType = StoryTokenType.WHITE_SPACE; + return; + } + else { + consume(INPUT_CHAR); + tokenType = StoryTokenType.STORY_DESCRIPTION; + return; + } + } + case IN_DISPATCH: { + if (consume(CRLF) || consume(SPACES)) { + tokenType = StoryTokenType.WHITE_SPACE; + return; + } + CharTree.Entry entry = charTree.lookup(buffer, position); + tokenType = tokenType(entry.value); + position += entry.length; + return; + } + case IN_SCENARIO: { + if (consume(CRLF)) { + tokenType = StoryTokenType.WHITE_SPACE; + // + CharTree.Entry entry = charTree.lookup(buffer, position); + if (entry.hasValue()) { + switch (entry.value) { + case Given: + case When: + case Then: + case And: + case Meta: + case ExamplesTable: + case Narrative: + case AsA: + case IWantTo: + case InOrderTo: + case Scenario: + state = State.IN_DISPATCH; + return; + case ExamplesTableHeaderSeparator: + case ExamplesTableValueSeparator: + state = State.IN_OTHER_TABLE; + return; + } + } + return; + } + else { + consume(INPUT_CHAR); + tokenType = StoryTokenType.SCENARIO_TEXT; + return; + } + } + case IN_META: { + CharTree.Entry entry = charTree.lookup(buffer, position); + if (entry.hasValue()) { + switch (entry.value) { + case MetaProperty: + position += entry.length; + consume(META_PROPERTY_CHARS); + tokenType = StoryTokenType.META_KEY; + return; + default: + tokenType = tokenType(entry.value); + position += entry.length; + return; + } + } + else if (consume(SPACES)) { + tokenType = StoryTokenType.WHITE_SPACE; + return; + } + else if (consume(CRLF)) { + tokenType = StoryTokenType.WHITE_SPACE; + + // + entry = charTree.lookup(buffer, position); + if (entry.hasValue()) { + switch (entry.value) { + case Given: + case When: + case Then: + case And: + case Meta: + case ExamplesTable: + case Narrative: + case AsA: + case IWantTo: + case InOrderTo: + case Scenario: + state = State.IN_DISPATCH; + return; + case ExamplesTableHeaderSeparator: + case ExamplesTableValueSeparator: + state = State.IN_OTHER_TABLE; + return; + + } + } + return; + } + else { + consumeUntil(META_PROPERTY_CHARS, keywords.metaProperty()); + tokenType = StoryTokenType.META_TEXT; + return; + } + } + case IN_STEP: { + if (consume(CRLF)) { + tokenType = StoryTokenType.WHITE_SPACE; + + // + CharTree.Entry entry = charTree.lookup(buffer, position); + if (entry.hasValue()) { + switch (entry.value) { + case Given: + case When: + case Then: + case And: + case Meta: + case ExamplesTable: + case Narrative: + case AsA: + case IWantTo: + case InOrderTo: + case Scenario: + state = State.IN_DISPATCH; + return; + case ExamplesTableHeaderSeparator: + case ExamplesTableValueSeparator: + state = State.IN_STEP_TABLE; + return; + } + } + return; + } + else { + consume(INPUT_CHAR); + tokenType = StoryTokenType.STEP_TEXT; + return; + } + } + case IN_OTHER_TABLE: + case IN_STEP_TABLE: + case IN_TABLE: { + if (consume(CRLF)) { + tokenType = StoryTokenType.WHITE_SPACE; + // + CharTree.Entry entry = charTree.lookup(buffer, position); + if (entry.hasValue()) { + switch (entry.value) { + case Given: + case When: + case Then: + case And: + case Meta: + case ExamplesTable: + case Narrative: + case AsA: + case IWantTo: + case InOrderTo: + case Scenario: + state = State.IN_DISPATCH; + return; + } + } + return; + } + else if (consume(keywords.examplesTableHeaderSeparator())) { + tokenType = StoryTokenType.TABLE_DELIM; + return; + } + else if (consume(keywords.examplesTableValueSeparator())) { + tokenType = StoryTokenType.TABLE_DELIM; + return; + } + else if (consumeUntil(INPUT_CHAR, keywords.examplesTableHeaderSeparator(), keywords.examplesTableValueSeparator())) { + tokenType = StoryTokenType.TABLE_CELL; + return; + } + } + case IN_EXAMPLES: + default: + throw new UnsupportedOperationException("State: " + state.name()); + } + } + + private CharSequence tokenText() { + return buffer.subSequence(this.currentTokenStart, this.position); + } + + private boolean matchesAhead(String text) { + if (position + text.length() > endOffset) { + return false; + } + for (int i = 0; i < text.length(); i++) { + if (text.charAt(i) != buffer.charAt(position + i)) { + return false; + } + } + return true; + } + + private boolean consume(String data) { + if (matchesAhead(data)) { + position += data.length(); + return true; + } + return false; + } + + private boolean consumeUntil(CharFilter filter, String stopWord) { + int previousPosition = position; + while (position < endOffset && !matchesAhead(stopWord) && filter.accept(buffer.charAt(position))) { + position++; + } + return position != previousPosition; + } + + private boolean consumeUntil(CharFilter filter, String stopWord1, String stopWord2) { + int previousPosition = position; + while (position < endOffset && !(matchesAhead(stopWord1) || matchesAhead(stopWord2)) + && filter.accept(buffer.charAt(position))) + { + position++; + } + return position != previousPosition; + } + + private boolean consume(CharFilter filter) { + int previousPosition = position; + while (position < endOffset && filter.accept(buffer.charAt(position))) { + position++; + } + return position != previousPosition; + } + + public State lexerState() { + return state; + } + + public interface CharFilter { + boolean accept(char c); + } + + private static CharFilter SPACES = new CharFilter() { + @Override + public boolean accept(char c) { + return c == ' ' || c == '\t'; + } + }; + + private static CharFilter CRLF = new CharFilter() { + @Override + public boolean accept(char c) { + return c == '\r' || c == '\n'; + } + }; + + private static CharFilter INPUT_CHAR = new CharFilter() { + @Override + public boolean accept(char c) { + return c != '\r' && c != '\n'; + } + }; + + private static CharFilter META_PROPERTY_CHARS = new CharFilter() { + @Override + public boolean accept(char c) { + return !SPACES.accept(c) && !CRLF.accept(c); + } + }; + + private IElementType tokenType(JBKeyword value) { + switch (value) { + case Given: + state = State.IN_STEP; + return StoryTokenType.STEP_TYPE_GIVEN; + case When: + state = State.IN_STEP; + return StoryTokenType.STEP_TYPE_WHEN; + case Then: + state = State.IN_STEP; + return StoryTokenType.STEP_TYPE_THEN; + case And: + state = State.IN_STEP; + return StoryTokenType.STEP_TYPE_AND; + case Ignorable: + case ExamplesTableIgnorableSeparator: + return StoryTokenType.COMMENT; + case Narrative: + case AsA: + case InOrderTo: + case IWantTo: + state = State.IN_STORY; + return StoryTokenType.NARRATIVE_TYPE; + case ExamplesTable: + return StoryTokenType.EXAMPLE_TYPE; + case ExamplesTableHeaderSeparator: + case ExamplesTableValueSeparator: + state = State.IN_TABLE; + return StoryTokenType.TABLE_DELIM; + case GivenStories: + return StoryTokenType.GIVEN_STORIES; + case Meta: + state = State.IN_META; + return StoryTokenType.META; + case Scenario: + state = State.IN_SCENARIO; + return StoryTokenType.SCENARIO_TYPE; + + case MetaProperty: + break; + case ExamplesTableRow: + break; + } + return StoryTokenType.BAD_CHARACTER; + } + + +} diff --git a/src/com/github/kumaraman21/intellijbehave/highlighter/StorySyntaxHighlighter.java b/src/com/github/kumaraman21/intellijbehave/highlighter/StorySyntaxHighlighter.java index ff5d8ec..2108814 100644 --- a/src/com/github/kumaraman21/intellijbehave/highlighter/StorySyntaxHighlighter.java +++ b/src/com/github/kumaraman21/intellijbehave/highlighter/StorySyntaxHighlighter.java @@ -16,60 +16,114 @@ package com.github.kumaraman21.intellijbehave.highlighter; import com.intellij.lexer.Lexer; +import com.intellij.openapi.editor.HighlighterColors; import com.intellij.openapi.editor.SyntaxHighlighterColors; import com.intellij.openapi.editor.colors.CodeInsightColors; import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.openapi.fileTypes.SyntaxHighlighterBase; import com.intellij.psi.tree.IElementType; import gnu.trove.THashMap; -import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Map; public class StorySyntaxHighlighter extends SyntaxHighlighterBase { - private static final Map KEYS; + private static final Map ATTRIBUTES = new THashMap(); + + @NotNull + @Override + public Lexer getHighlightingLexer() { + return new StorySyntaxHighlightingLexer(); + } + + @NotNull + @Override + public TextAttributesKey[] getTokenHighlights(IElementType tokenType) { + return pack(ATTRIBUTES.get(tokenType)); + } - private static final TextAttributesKey STORY_DESCRIPTION_ATTRIBUTES - = TextAttributesKey.createTextAttributesKey("STORY_DESCRIPTION_ATTRIBUTES", - SyntaxHighlighterColors.NUMBER.getDefaultAttributes()); - private static final TextAttributesKey SCENARIO_TEXT_ATTRIBUTES - = TextAttributesKey.createTextAttributesKey("SCENARIO_TEXT_ATTRIBUTES", - CodeInsightColors.STATIC_FIELD_ATTRIBUTES.getDefaultAttributes()); - private static final TextAttributesKey STEP_TYPE_ATTRIBUTES - = TextAttributesKey.createTextAttributesKey("STEP_TYPE_ATTRIBUTES", - SyntaxHighlighterColors.KEYWORD.getDefaultAttributes()); - private static final TextAttributesKey STEP_TEXT_ATTRIBUTES - = TextAttributesKey.createTextAttributesKey("STEP_TEXT_ATTRIBUTES", - SyntaxHighlighterColors.STRING.getDefaultAttributes()); - private static final TextAttributesKey COMMENT_ATTRIBUTES - = TextAttributesKey.createTextAttributesKey("COMMENT_ATTRIBUTES", - SyntaxHighlighterColors.LINE_COMMENT.getDefaultAttributes()); - private static final TextAttributesKey BAD_CHARACTER_ATTRIBUTES - = TextAttributesKey.createTextAttributesKey("BAD_CHARACTER_ATTRIBUTES", - SyntaxHighlighterColors.INVALID_STRING_ESCAPE.getDefaultAttributes()); + @NonNls + public static final String STORY_DESCRIPTION_ID = "JBEHAVE.STORY_DESCRIPTION"; + @NonNls + public static final String SCENARIO_TYPE_ID = "JBEHAVE.SCENARIO_TYPE"; + @NonNls + public static final String SCENARIO_TEXT_ID = "JBEHAVE.SCENARIO_TEXT"; + @NonNls + public static final String STEP_TYPE_ID = "JBEHAVE.STEP_TYPE"; + @NonNls + public static final String STEP_TEXT_ID = "JBEHAVE.STEP_TEXT"; + @NonNls + public static final String TABLE_DELIM_ID = "JBEHAVE.TABLE_DELIM"; + @NonNls + public static final String TABLE_CELL_ID = "JBEHAVE.TABLE_CELL"; + @NonNls + public static final String META_TYPE_ID = "JBEHAVE.META_TYPE"; + @NonNls + public static final String META_KEY_ID = "JBEHAVE.META_KEY"; + @NonNls + public static final String META_TEXT_ID = "JBEHAVE.META_TEXT"; + @NonNls + public static final String LINE_COMMENT_ID = "JBEHAVE.COMMENT"; + @NonNls + public static final String BAD_CHARACTER_ID = "JBEHAVE.BAD_CHARACTER"; - static { - KEYS = new THashMap(); + // Registering TextAttributes + static { + createKey(STORY_DESCRIPTION_ID, SyntaxHighlighterColors.NUMBER); + createKey(SCENARIO_TYPE_ID, CodeInsightColors.STATIC_FIELD_ATTRIBUTES); + createKey(SCENARIO_TEXT_ID, CodeInsightColors.STATIC_FIELD_ATTRIBUTES); + createKey(STEP_TYPE_ID, SyntaxHighlighterColors.KEYWORD); + createKey(STEP_TEXT_ID, HighlighterColors.TEXT); + createKey(TABLE_DELIM_ID, SyntaxHighlighterColors.BRACES); + createKey(TABLE_CELL_ID, SyntaxHighlighterColors.STRING); + createKey(META_TYPE_ID, SyntaxHighlighterColors.KEYWORD); + createKey(META_KEY_ID, SyntaxHighlighterColors.STRING); + createKey(META_TEXT_ID, SyntaxHighlighterColors.STRING); + createKey(LINE_COMMENT_ID, SyntaxHighlighterColors.LINE_COMMENT); + createKey(BAD_CHARACTER_ID, SyntaxHighlighterColors.INVALID_STRING_ESCAPE); + } - KEYS.put(StoryTokenType.STORY_DESCRIPTION, STORY_DESCRIPTION_ATTRIBUTES); - KEYS.put(StoryTokenType.SCENARIO_TEXT, SCENARIO_TEXT_ATTRIBUTES); - KEYS.put(StoryTokenType.STEP_TYPE, STEP_TYPE_ATTRIBUTES); - KEYS.put(StoryTokenType.STEP_TEXT, STEP_TEXT_ATTRIBUTES); - KEYS.put(StoryTokenType.TABLE_ROW, STEP_TEXT_ATTRIBUTES); - KEYS.put(StoryTokenType.COMMENT, COMMENT_ATTRIBUTES); - KEYS.put(StoryTokenType.BAD_CHARACTER, BAD_CHARACTER_ATTRIBUTES); - } + public static TextAttributesKey STORY_DESCRIPTION = TextAttributesKey.createTextAttributesKey(STORY_DESCRIPTION_ID); + public static TextAttributesKey SCENARIO_TYPE = TextAttributesKey.createTextAttributesKey(SCENARIO_TYPE_ID); + public static TextAttributesKey SCENARIO_TEXT = TextAttributesKey.createTextAttributesKey(SCENARIO_TEXT_ID); + public static TextAttributesKey STEP_TYPE = TextAttributesKey.createTextAttributesKey(STEP_TYPE_ID); + public static TextAttributesKey STEP_TEXT = TextAttributesKey.createTextAttributesKey(STEP_TEXT_ID); + public static TextAttributesKey TABLE_DELIM = TextAttributesKey.createTextAttributesKey(TABLE_DELIM_ID); + public static TextAttributesKey TABLE_CELL = TextAttributesKey.createTextAttributesKey(TABLE_CELL_ID); + public static TextAttributesKey META_TYPE = TextAttributesKey.createTextAttributesKey(META_TYPE_ID); + public static TextAttributesKey META_KEY = TextAttributesKey.createTextAttributesKey(META_KEY_ID); + public static TextAttributesKey META_TEXT = TextAttributesKey.createTextAttributesKey(META_TEXT_ID); + public static TextAttributesKey LINE_COMMENT = TextAttributesKey.createTextAttributesKey(LINE_COMMENT_ID); + public static TextAttributesKey BAD_CHARACTER = TextAttributesKey.createTextAttributesKey(BAD_CHARACTER_ID); - @NotNull - @Override - public Lexer getHighlightingLexer() { - return new StorySyntaxHighlightingLexer(); - } + static { + ATTRIBUTES.put(StoryTokenType.STORY_DESCRIPTION, STORY_DESCRIPTION); + ATTRIBUTES.put(StoryTokenType.NARRATIVE_TYPE, STORY_DESCRIPTION); + ATTRIBUTES.put(StoryTokenType.NARRATIVE_TEXT, STORY_DESCRIPTION); + ATTRIBUTES.put(StoryTokenType.SCENARIO_TYPE, SCENARIO_TYPE); + ATTRIBUTES.put(StoryTokenType.SCENARIO_TEXT, SCENARIO_TEXT); + ATTRIBUTES.put(StoryTokenType.STEP_TYPE, STEP_TYPE); + ATTRIBUTES.put(StoryTokenType.STEP_TYPE_GIVEN, STEP_TYPE); + ATTRIBUTES.put(StoryTokenType.STEP_TYPE_WHEN, STEP_TYPE); + ATTRIBUTES.put(StoryTokenType.STEP_TYPE_THEN, STEP_TYPE); + ATTRIBUTES.put(StoryTokenType.STEP_TYPE_AND, STEP_TYPE); + ATTRIBUTES.put(StoryTokenType.STEP_TEXT, STEP_TEXT); + ATTRIBUTES.put(StoryTokenType.TABLE_DELIM, TABLE_DELIM); + ATTRIBUTES.put(StoryTokenType.TABLE_CELL, TABLE_CELL); + ATTRIBUTES.put(StoryTokenType.META, META_TYPE); + ATTRIBUTES.put(StoryTokenType.META_KEY, META_KEY); + ATTRIBUTES.put(StoryTokenType.META_TEXT, META_TEXT); + ATTRIBUTES.put(StoryTokenType.COMMENT, LINE_COMMENT); + ATTRIBUTES.put(StoryTokenType.COMMENT_WITH_LOCALE, LINE_COMMENT); + ATTRIBUTES.put(StoryTokenType.BAD_CHARACTER, BAD_CHARACTER); + } - @NotNull - @Override - public TextAttributesKey[] getTokenHighlights(IElementType tokenType) { - return new TextAttributesKey[]{KEYS.get(tokenType)}; - } + private static TextAttributesKey createKey(String externalName, TextAttributesKey textAttributesKey) { + return TextAttributesKey.createTextAttributesKey(externalName, textAttributesKey.getDefaultAttributes()); + } } diff --git a/src/com/github/kumaraman21/intellijbehave/highlighter/StorySyntaxHighlightingLexer.java b/src/com/github/kumaraman21/intellijbehave/highlighter/StorySyntaxHighlightingLexer.java index 0a98ab0..92b8252 100644 --- a/src/com/github/kumaraman21/intellijbehave/highlighter/StorySyntaxHighlightingLexer.java +++ b/src/com/github/kumaraman21/intellijbehave/highlighter/StorySyntaxHighlightingLexer.java @@ -19,6 +19,6 @@ public class StorySyntaxHighlightingLexer extends LayeredLexer { public StorySyntaxHighlightingLexer() { - super(new StoryLexer()); + super(new StoryLexerFactory().createLexer()); } } diff --git a/src/com/github/kumaraman21/intellijbehave/highlighter/StoryTokenType.java b/src/com/github/kumaraman21/intellijbehave/highlighter/StoryTokenType.java index ef27b64..0cd7377 100644 --- a/src/com/github/kumaraman21/intellijbehave/highlighter/StoryTokenType.java +++ b/src/com/github/kumaraman21/intellijbehave/highlighter/StoryTokenType.java @@ -15,28 +15,71 @@ */ package com.github.kumaraman21.intellijbehave.highlighter; +import static com.github.kumaraman21.intellijbehave.language.StoryFileType.STORY_FILE_TYPE; + import com.intellij.psi.TokenType; import com.intellij.psi.tree.IElementType; -import org.jetbrains.annotations.NonNls; +import com.intellij.psi.tree.TokenSet; -import static com.github.kumaraman21.intellijbehave.language.StoryFileType.STORY_FILE_TYPE; +import org.jetbrains.annotations.NonNls; public class StoryTokenType extends IElementType { - public StoryTokenType(@NonNls String debugName) { - super(debugName, STORY_FILE_TYPE.getLanguage()); - } + public static final IElementType WHITE_SPACE = TokenType.WHITE_SPACE; + public static final IElementType BAD_CHARACTER = TokenType.BAD_CHARACTER; + + public static final IElementType STORY_DESCRIPTION = new StoryTokenType("STORY_DESCRIPTION"); + public static final IElementType SCENARIO_TYPE = new StoryTokenType("SCENARIO_TYPE"); + public static final IElementType SCENARIO_TEXT = new StoryTokenType("SCENARIO_TEXT"); + + public static final IElementType STEP_TYPE = new StoryTokenType("STEP_TYPE"); + public static final IElementType STEP_TYPE_GIVEN = new StoryTokenType("STEP_TYPE_GIVEN"); + public static final IElementType STEP_TYPE_WHEN = new StoryTokenType("STEP_TYPE_WHEN"); + public static final IElementType STEP_TYPE_THEN = new StoryTokenType("STEP_TYPE_THEN"); + public static final IElementType STEP_TYPE_AND = new StoryTokenType("STEP_TYPE_AND"); + + public static final TokenSet STEP_TYPES = TokenSet.create(STEP_TYPE, STEP_TYPE_GIVEN, STEP_TYPE_WHEN, STEP_TYPE_THEN, STEP_TYPE_AND); + + public static final IElementType STEP_TEXT = new StoryTokenType("STEP_TEXT"); + + public static final IElementType TABLE_DELIM = new StoryTokenType("TABLE_DELIM"); + public static final IElementType TABLE_CELL = new StoryTokenType("TABLE_CELL"); + + public static final IElementType COMMENT = new StoryTokenType("COMMENT"); + public static final IElementType COMMENT_WITH_LOCALE = new StoryTokenType("COMMENT_WITH_LOCALE"); + + public static final IElementType META = new StoryTokenType("META"); + public static final IElementType META_KEY = new StoryTokenType("META_KEY"); + public static final IElementType META_TEXT = new StoryTokenType("META_TEXT"); + public static final IElementType EXAMPLE_TYPE = new StoryTokenType("EXAMPLE_TYPE"); + public static final IElementType GIVEN_STORIES = new StoryTokenType("GIVEN_STORIES"); + + public static final IElementType NARRATIVE_TYPE = new StoryTokenType("NARRATIVE_TYPE"); + public static final IElementType NARRATIVE_TEXT = new StoryTokenType("NARRATIVE_TEXT"); - public static final IElementType WHITE_SPACE = TokenType.WHITE_SPACE; - public static final IElementType BAD_CHARACTER = TokenType.BAD_CHARACTER; + private final String key; + public StoryTokenType(@NonNls String debugName) { + super(debugName, STORY_FILE_TYPE.getLanguage()); + this.key = debugName; + } - public static final IElementType STORY_DESCRIPTION = new StoryTokenType("STORY_DESCRIPTION"); - public static final IElementType SCENARIO_TEXT = new StoryTokenType("SCENARIO_TEXT"); + @Override + public boolean equals(Object other) { + return (other instanceof StoryTokenType) + && ((StoryTokenType)other).key.equals(key); + } - public static final IElementType STEP_TYPE = new StoryTokenType("STEP_TYPE"); - public static final IElementType STEP_TEXT = new StoryTokenType("STEP_TEXT"); + @Override + public int hashCode() { + return key.hashCode(); + } - public static final IElementType TABLE_ROW = new StoryTokenType("TABLE_ROW"); + public static boolean isStepType(IElementType tokenType) { + return tokenType == STEP_TYPE_GIVEN + || tokenType == STEP_TYPE_WHEN + || tokenType == STEP_TYPE_THEN + || tokenType == STEP_TYPE_AND + || tokenType == STEP_TYPE; + } - public static final IElementType COMMENT = new StoryTokenType("COMMENT"); } diff --git a/src/com/github/kumaraman21/intellijbehave/highlighter/_StoryLexer.java b/src/com/github/kumaraman21/intellijbehave/highlighter/_StoryLexer.java index a2d1c06..4c9f97c 100644 --- a/src/com/github/kumaraman21/intellijbehave/highlighter/_StoryLexer.java +++ b/src/com/github/kumaraman21/intellijbehave/highlighter/_StoryLexer.java @@ -1,15 +1,16 @@ -/* The following code was generated by JFlex 1.4.3 on 12/21/11 8:00 PM */ +/* The following code was generated by JFlex 1.4.3 on 27/08/12 18:33 */ package com.github.kumaraman21.intellijbehave.highlighter; import com.intellij.lexer.FlexLexer; import com.intellij.psi.tree.IElementType; +import java.util.Stack; /** * This class is a scanner generated by * JFlex 1.4.3 - * on 12/21/11 8:00 PM from the specification file + * on 27/08/12 18:33 from the specification file * Story.flex */ class _StoryLexer implements FlexLexer { @@ -17,9 +18,14 @@ class _StoryLexer implements FlexLexer { private static final int ZZ_BUFFERSIZE = 16384; /** lexical states */ - public static final int IN_SCENARIO = 2; + public static final int IN_SCENARIO = 6; + public static final int IN_STORY = 4; public static final int YYINITIAL = 0; - public static final int IN_STEP = 4; + public static final int IN_TABLE = 12; + public static final int IN_DIRECTIVE = 2; + public static final int IN_STEP = 8; + public static final int IN_META = 10; + public static final int IN_EXAMPLES = 14; /** * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l @@ -28,18 +34,19 @@ class _StoryLexer implements FlexLexer { * l is of the form l = 2*k, k a non negative integer */ private static final int ZZ_LEXSTATE[] = { - 0, 0, 1, 1, 2, 2 + 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7 }; /** * Translates characters to character classes */ private static final String ZZ_CMAP_PACKED = - "\11\0\1\3\1\1\1\0\1\3\1\2\22\0\1\3\1\4\13\0"+ - "\1\5\14\0\1\16\6\0\1\23\5\0\1\17\13\0\1\6\1\21"+ - "\2\0\1\21\11\0\1\12\1\0\1\7\1\24\1\10\2\0\1\22"+ - "\1\14\4\0\1\11\1\15\2\0\1\13\3\0\1\20\5\0\1\25"+ - "\uff83\0"; + "\11\0\1\3\1\2\1\0\1\3\1\1\22\0\1\17\1\36\13\0"+ + "\1\37\14\0\1\16\5\0\1\5\1\34\3\0\1\22\1\0\1\30"+ + "\5\0\1\20\5\0\1\6\1\32\2\0\1\32\11\0\1\12\1\0"+ + "\1\7\1\35\1\10\2\0\1\33\1\14\2\0\1\26\1\24\1\11"+ + "\1\15\1\25\1\0\1\13\1\27\1\21\1\0\1\31\1\0\1\23"+ + "\3\0\1\4\uff83\0"; /** * Translates characters to character classes @@ -52,14 +59,15 @@ class _StoryLexer implements FlexLexer { private static final int [] ZZ_ACTION = zzUnpackAction(); private static final String ZZ_ACTION_PACKED_0 = - "\2\1\1\2\1\1\1\3\2\1\1\3\2\1\2\2"+ - "\1\3\1\0\1\1\1\3\3\0\1\4\4\1\1\4"+ - "\1\1\1\0\1\1\3\0\3\1\1\5\1\0\1\1"+ - "\2\0\2\1\1\0\1\1\2\6\1\0\1\1\1\0"+ - "\1\1\1\0\1\1\1\0\1\1\1\0\1\1\2\7"; + "\10\0\1\1\2\2\10\1\1\3\1\4\1\5\7\3"+ + "\2\4\1\6\1\7\1\10\1\11\1\12\2\13\1\14"+ + "\1\4\1\15\7\1\7\0\1\15\7\0\5\1\5\0"+ + "\1\16\7\0\3\1\3\0\1\17\5\0\2\1\1\0"+ + "\1\20\4\0\2\1\4\0\2\1\4\0\1\1\5\0"+ + "\1\21\1\0\1\22"; private static int [] zzUnpackAction() { - int [] result = new int[57]; + int [] result = new int[122]; int offset = 0; offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result); return result; @@ -84,17 +92,25 @@ private static int zzUnpackAction(String packed, int offset, int [] result) { private static final int [] ZZ_ROWMAP = zzUnpackRowMap(); private static final String ZZ_ROWMAP_PACKED_0 = - "\0\0\0\26\0\54\0\102\0\130\0\156\0\204\0\232"+ - "\0\260\0\306\0\334\0\362\0\u0108\0\u011e\0\u0134\0\u014a"+ - "\0\u0160\0\u0176\0\u018c\0\u01a2\0\u01b8\0\u01ce\0\u01e4\0\u01fa"+ - "\0\u0210\0\u0226\0\u023c\0\u0252\0\u0268\0\u027e\0\u0294\0\u02aa"+ - "\0\u02c0\0\u02d6\0\u02ec\0\u0302\0\u0318\0\u032e\0\u0344\0\u035a"+ - "\0\u0370\0\u0386\0\u039c\0\u03b2\0\102\0\u03c8\0\u03de\0\u03f4"+ - "\0\u040a\0\u0420\0\u0436\0\u044c\0\u0462\0\u0478\0\u048e\0\u0478"+ - "\0\u048e"; + "\0\0\0\40\0\100\0\140\0\200\0\240\0\300\0\340"+ + "\0\u0100\0\u0120\0\u0140\0\u0160\0\u0180\0\u01a0\0\u01c0\0\u01e0"+ + "\0\u0200\0\u0220\0\u0240\0\u0140\0\u0260\0\u0140\0\u0280\0\u02a0"+ + "\0\u02c0\0\u02e0\0\u0300\0\u0320\0\u0340\0\u0360\0\u0380\0\u03a0"+ + "\0\u03c0\0\u03e0\0\u0400\0\u0420\0\u0440\0\u0140\0\u0140\0\u0140"+ + "\0\u0160\0\u0460\0\u0480\0\u04a0\0\u04c0\0\u04e0\0\u0500\0\u0520"+ + "\0\u0540\0\u0560\0\u0580\0\u05a0\0\u05c0\0\u05e0\0\u0600\0\u0140"+ + "\0\u0620\0\u0640\0\u0660\0\u0680\0\u06a0\0\u06c0\0\u06e0\0\u0700"+ + "\0\u0720\0\u0740\0\u0760\0\u0780\0\u07a0\0\u07c0\0\u07e0\0\u0800"+ + "\0\u0820\0\u0840\0\u0860\0\u0880\0\u08a0\0\u08c0\0\u08e0\0\u0900"+ + "\0\u0920\0\u0940\0\u0960\0\u0980\0\u09a0\0\u09c0\0\u09e0\0\u0140"+ + "\0\u0a00\0\u0a20\0\u0a40\0\u0a60\0\u0a80\0\u0aa0\0\u0ac0\0\u0ae0"+ + "\0\u0140\0\u0b00\0\u0b20\0\u0b40\0\u0b60\0\u0b80\0\u0ba0\0\u0bc0"+ + "\0\u0be0\0\u0c00\0\u0c20\0\u0c40\0\u0c60\0\u0c80\0\u0ca0\0\u0cc0"+ + "\0\u0ce0\0\u0d00\0\u0d20\0\u0d40\0\u0d60\0\u0d80\0\u0da0\0\u0140"+ + "\0\u0dc0\0\u0140"; private static int [] zzUnpackRowMap() { - int [] result = new int[57]; + int [] result = new int[122]; int offset = 0; offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result); return result; @@ -117,37 +133,59 @@ private static int zzUnpackRowMap(String packed, int offset, int [] result) { private static final int [] ZZ_TRANS = zzUnpackTrans(); private static final String ZZ_TRANS_PACKED_0 = - "\1\4\1\5\1\6\1\7\23\4\1\10\1\11\1\7"+ - "\1\12\21\4\1\13\2\5\1\14\22\13\1\4\1\0"+ - "\24\4\1\0\2\5\1\15\2\0\1\16\17\0\1\4"+ - "\1\5\1\6\1\7\2\4\1\17\20\4\1\15\2\7"+ - "\22\4\1\0\2\10\1\20\2\0\1\16\10\0\1\21"+ - "\1\0\1\22\1\0\1\23\1\0\1\24\1\4\1\10"+ - "\1\11\1\25\2\4\1\17\10\4\1\26\1\4\1\27"+ - "\1\4\1\30\1\4\1\31\1\4\1\0\3\4\1\32"+ - "\20\4\1\13\2\0\24\13\2\15\1\14\22\13\1\0"+ - "\3\15\31\0\1\33\16\0\1\4\1\0\5\4\1\34"+ - "\16\4\1\0\3\20\21\0\1\24\14\0\1\35\33\0"+ - "\1\36\14\0\1\37\14\0\1\24\2\0\23\24\1\4"+ - "\1\20\2\25\21\4\1\31\1\4\1\0\12\4\1\40"+ - "\12\4\1\0\20\4\1\41\4\4\1\0\7\4\1\42"+ - "\14\4\1\31\1\0\1\4\23\31\1\4\1\0\3\4"+ - "\1\43\20\4\10\0\1\44\15\0\1\4\1\0\6\4"+ - "\1\45\15\4\20\0\1\36\15\0\1\46\41\0\1\47"+ - "\1\0\1\4\1\0\16\4\1\41\6\4\1\0\6\4"+ - "\1\50\16\4\1\0\22\4\1\51\1\4\1\43\1\0"+ - "\1\4\23\43\11\0\1\52\14\0\1\4\1\0\7\4"+ - "\1\53\14\4\11\0\1\47\15\0\3\54\22\0\1\4"+ - "\1\0\7\4\1\51\15\4\1\54\2\55\22\4\12\0"+ - "\1\56\13\0\1\4\1\0\10\4\1\57\13\4\41\0"+ - "\1\60\12\0\1\4\1\0\11\4\1\61\12\4\14\0"+ - "\1\62\11\0\1\4\1\0\12\4\1\63\11\4\15\0"+ - "\1\64\10\0\1\4\1\0\13\4\1\65\10\4\16\0"+ - "\1\66\7\0\1\4\1\0\14\4\1\67\7\4\1\70"+ - "\2\0\23\70\1\71\1\0\1\4\23\71"; + "\1\11\1\12\1\13\1\11\1\14\1\11\1\15\11\11"+ + "\1\16\1\11\1\17\5\11\1\20\1\11\1\21\1\11"+ + "\1\22\1\11\1\23\1\11\1\24\3\25\1\26\1\24"+ + "\1\27\10\24\1\25\1\30\1\24\1\31\5\24\1\32"+ + "\1\24\1\33\1\24\1\34\1\24\1\35\1\24\1\11"+ + "\1\36\1\37\35\11\1\40\1\36\1\37\35\40\1\41"+ + "\1\36\1\37\35\41\1\42\1\36\1\37\2\42\1\43"+ + "\32\42\1\44\1\45\1\46\1\44\1\47\33\44\1\24"+ + "\1\36\1\37\1\50\13\24\1\50\20\24\1\11\2\0"+ + "\35\11\2\0\1\13\75\0\1\51\2\0\35\51\1\11"+ + "\2\0\4\11\1\52\31\11\2\0\5\11\1\53\30\11"+ + "\2\0\20\11\1\54\15\11\2\0\11\11\1\55\24\11"+ + "\2\0\30\11\1\56\5\11\2\0\6\11\1\57\27\11"+ + "\2\0\34\11\1\60\1\0\3\25\13\0\1\25\27\0"+ + "\1\61\40\0\1\62\52\0\1\63\30\0\1\64\56\0"+ + "\1\65\15\0\1\66\65\0\1\67\2\0\1\37\1\0"+ + "\1\70\1\0\1\71\11\0\1\72\1\0\1\73\5\0"+ + "\1\74\1\0\1\75\1\0\1\76\1\0\1\77\5\0"+ + "\1\70\1\0\1\71\11\0\1\72\1\0\1\73\5\0"+ + "\1\74\1\0\1\75\1\0\1\76\1\0\1\77\1\0"+ + "\1\40\2\0\35\40\1\41\2\0\35\41\1\42\2\0"+ + "\2\42\1\0\32\42\1\43\3\0\13\43\1\0\20\43"+ + "\1\44\2\0\1\44\1\0\33\44\2\0\1\46\35\0"+ + "\1\11\2\0\5\11\1\100\30\11\2\0\16\11\1\101"+ + "\17\11\2\0\7\11\1\102\26\11\2\0\26\11\1\56"+ + "\7\11\2\0\5\11\1\103\30\11\2\0\32\11\1\104"+ + "\3\11\2\0\34\11\1\14\10\0\1\105\50\0\1\106"+ + "\30\0\1\107\56\0\1\65\16\0\1\110\64\0\1\111"+ + "\41\0\1\112\7\0\1\113\40\0\1\114\52\0\1\115"+ + "\30\0\1\116\56\0\1\117\15\0\1\120\65\0\1\121"+ + "\1\11\2\0\6\11\1\122\27\11\2\0\7\11\1\123"+ + "\26\11\2\0\21\11\1\124\14\11\2\0\6\11\1\104"+ + "\27\11\2\0\14\11\1\14\20\11\11\0\1\125\40\0"+ + "\1\126\51\0\1\127\24\0\1\111\45\0\1\130\20\0"+ + "\1\112\2\0\35\112\10\0\1\131\50\0\1\132\30\0"+ + "\1\133\56\0\1\117\16\0\1\134\64\0\1\135\41\0"+ + "\1\70\1\11\2\0\7\11\1\136\26\11\2\0\13\11"+ + "\1\14\22\11\2\0\22\11\1\137\12\11\12\0\1\140"+ + "\43\0\1\141\46\0\1\142\23\0\1\143\40\0\1\144"+ + "\51\0\1\145\24\0\1\135\45\0\1\70\20\0\1\11"+ + "\2\0\10\11\1\146\25\11\2\0\23\11\1\147\11\11"+ + "\13\0\1\150\52\0\1\151\23\0\1\152\43\0\1\70"+ + "\46\0\1\153\12\0\1\11\2\0\11\11\1\154\24\11"+ + "\2\0\5\11\1\155\27\11\14\0\1\156\33\0\1\157"+ + "\42\0\1\160\52\0\1\161\11\0\1\11\2\0\12\11"+ + "\1\162\23\11\2\0\24\11\1\123\10\11\15\0\1\163"+ + "\51\0\1\164\24\0\1\165\33\0\1\166\27\0\1\11"+ + "\2\0\13\11\1\104\21\11\16\0\1\167\37\0\1\170"+ + "\36\0\1\171\51\0\1\144\27\0\1\172\36\0\1\135"+ + "\21\0"; private static int [] zzUnpackTrans() { - int [] result = new int[1188]; + int [] result = new int[3552]; int offset = 0; offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result); return result; @@ -188,13 +226,14 @@ private static int zzUnpackTrans(String packed, int offset, int [] result) { private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute(); private static final String ZZ_ATTRIBUTE_PACKED_0 = - "\15\1\1\0\2\1\3\0\7\1\1\0\1\1\3\0"+ - "\4\1\1\0\1\1\2\0\2\1\1\0\1\1\1\11"+ - "\1\1\1\0\1\1\1\0\1\1\1\0\1\1\1\0"+ - "\1\1\1\0\3\1"; + "\10\0\2\1\1\11\10\1\1\11\1\1\1\11\17\1"+ + "\3\11\10\1\7\0\1\11\7\0\5\1\5\0\1\1"+ + "\7\0\3\1\3\0\1\11\5\0\2\1\1\0\1\11"+ + "\4\0\2\1\4\0\2\1\4\0\1\1\5\0\1\11"+ + "\1\0\1\11"; private static int [] zzUnpackAttribute() { - int [] result = new int[57]; + int [] result = new int[122]; int offset = 0; offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result); return result; @@ -252,6 +291,53 @@ the source of the yytext() string */ /** denotes if the user-EOF-code has already been executed */ private boolean zzEOFDone; + /* user code: */ + private Stack yystates = new Stack () {{ push(YYINITIAL); }}; + public boolean trace = false; + + public void yystatePush(int yystate) { + if(trace) System.out.println(">>>> PUSH: " + LexicalState.fromLexer(yystate) + " [" + reverseAndMap(yystates) + "]"); + yybegin(yystate); + yystates.push(yystate); + } + + private String reverseAndMap(Stack yystates) { + StringBuilder builder = new StringBuilder(); + for(int i=yystates.size()-1; i>=0; i--) { + if(builder.length()>0) + builder.append(", "); + builder.append(LexicalState.fromLexer(yystates.get(i))); + } + return builder.toString(); + } + + public void yystatePopNPush(int yystate) { + yystatePopNPush(1, yystate); + } + + public void yystatePopNPush(int nb, int yystate) { + if(trace) System.out.println(">>>> POP'n PUSH : #" + nb + ", " + LexicalState.fromLexer(yystate) + " [" + reverseAndMap(yystates) + "]"); + for (int i = 0; i < nb; i++) { + yystatePop(); + } + yystatePush(yystate); + } + + public int yystatePop() { + int popped = yystates.pop(); + if(trace) System.out.println(">>>> POP : " + LexicalState.fromLexer(popped) + " [" + reverseAndMap(yystates) + "]"); + if(!yystates.isEmpty()) { + yybegin(yystates.peek()); + }// otherwise hopes a push will follow right after + return popped; + } + + public boolean checkAhead(char c) { + if (zzMarkedPos >= zzBuffer.length()) + return false; + return zzBuffer.charAt(zzMarkedPos) == c; + } + _StoryLexer(java.io.Reader in) { this.zzReader = in; @@ -277,7 +363,7 @@ the source of the yytext() string */ char [] map = new char[0x10000]; int i = 0; /* index in packed string */ int j = 0; /* index in unpacked array */ - while (i < 82) { + while (i < 106) { int count = packed.charAt(i++); char value = packed.charAt(i++); do map[j++] = value; while (--count > 0); @@ -416,7 +502,8 @@ public void yypushback(int number) { private void zzDoEOF() { if (!zzEOFDone) { zzEOFDone = true; - + return; + } } @@ -499,34 +586,78 @@ else if (zzAtEOF) { zzMarkedPos = zzMarkedPosL; switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) { - case 4: - { yybegin(IN_SCENARIO); return StoryTokenType.TABLE_ROW; + case 10: + { return StoryTokenType.TABLE_CELL; + } + case 19: break; + case 15: + { yystatePopNPush(2, IN_STEP); return StoryTokenType.STEP_TYPE; + } + case 20: break; + case 14: + { yystatePop(); return StoryTokenType.COMMENT; + } + case 21: break; + case 9: + { return StoryTokenType.META_KEY; } - case 8: break; + case 22: break; case 2: - { yybegin(IN_SCENARIO); return StoryTokenType.STEP_TEXT; + { yystatePush(IN_STORY); yypushback(yytext().length()); } - case 9: break; - case 1: - { return StoryTokenType.STORY_DESCRIPTION; + case 23: break; + case 7: + { return StoryTokenType.STEP_TEXT; } - case 10: break; + case 24: break; + case 11: + { yystatePop(); yypushback(1); + } + case 25: break; case 5: - { return StoryTokenType.COMMENT; + { yystatePopNPush(1, IN_TABLE); return StoryTokenType.TABLE_DELIM; } - case 11: break; - case 3: - { return StoryTokenType.WHITE_SPACE; + case 26: break; + case 13: + { yystatePush(IN_DIRECTIVE); yypushback(yytext().length()); } - case 12: break; - case 7: - { yybegin(IN_SCENARIO); return StoryTokenType.SCENARIO_TEXT; + case 27: break; + case 8: + { return StoryTokenType.META_TEXT; + } + case 28: break; + case 12: + { return StoryTokenType.TABLE_DELIM; } - case 13: break; + case 29: break; case 6: - { yybegin(IN_STEP); return StoryTokenType.STEP_TYPE; + { return StoryTokenType.SCENARIO_TEXT; + } + case 30: break; + case 17: + { yystatePopNPush(2, IN_EXAMPLES); return StoryTokenType.EXAMPLE_TYPE; + } + case 31: break; + case 1: + { return StoryTokenType.STORY_DESCRIPTION; + } + case 32: break; + case 3: + { return StoryTokenType.BAD_CHARACTER; + } + case 33: break; + case 16: + { yystatePopNPush(2, IN_META); return StoryTokenType.META; + } + case 34: break; + case 18: + { yystatePopNPush(2, IN_SCENARIO); return StoryTokenType.SCENARIO_TYPE; + } + case 35: break; + case 4: + { return StoryTokenType.WHITE_SPACE; } - case 14: break; + case 36: break; default: if (zzInput == YYEOF && zzStartRead == zzCurrentPos) { zzAtEOF = true; diff --git a/src/com/github/kumaraman21/intellijbehave/highlighter/generateLexer.sh b/src/com/github/kumaraman21/intellijbehave/highlighter/generateLexer.sh new file mode 100755 index 0000000..7f81348 --- /dev/null +++ b/src/com/github/kumaraman21/intellijbehave/highlighter/generateLexer.sh @@ -0,0 +1,5 @@ +FLEX_DIR=~/Projects/intellij/tools/jflex-1.4.3/bin + +$FLEX_DIR/jflex-idea --skel "$FLEX_DIR/../../idea-flex.skeleton" Story.flex +cat _StoryLexer.java | sed 's/zzBufferL\[zzCurrentPosL\+\+\]/zzBufferL.charAt(zzCurrentPosL\+\+)/' > _StoryLexer.tmp +mv _StoryLexer.tmp _StoryLexer.java diff --git a/src/com/github/kumaraman21/intellijbehave/parser/StepPsiElement.java b/src/com/github/kumaraman21/intellijbehave/parser/StepPsiElement.java index 2d1fb41..e46aff1 100644 --- a/src/com/github/kumaraman21/intellijbehave/parser/StepPsiElement.java +++ b/src/com/github/kumaraman21/intellijbehave/parser/StepPsiElement.java @@ -15,37 +15,64 @@ */ package com.github.kumaraman21.intellijbehave.parser; +import static org.apache.commons.lang.StringUtils.trim; + +import com.github.kumaraman21.intellijbehave.highlighter.StoryTokenType; import com.github.kumaraman21.intellijbehave.resolver.StepPsiReference; import com.intellij.extapi.psi.ASTWrapperPsiElement; import com.intellij.lang.ASTNode; -import com.intellij.psi.PsiReference; + import org.jbehave.core.steps.StepType; import org.jetbrains.annotations.NotNull; - -import static org.apache.commons.lang.StringUtils.*; +import org.jetbrains.annotations.Nullable; public class StepPsiElement extends ASTWrapperPsiElement { - private StepType stepType; + private StepType stepType; + + public StepPsiElement(@NotNull ASTNode node, StepType stepType) { + super(node); + this.stepType = stepType; + } + + @Override + @NotNull + public StepPsiReference getReference() { + return new StepPsiReference(this); + } + + public StepType getStepType() { + return stepType; + } - public StepPsiElement(@NotNull ASTNode node, StepType stepType) { - super(node); - this.stepType = stepType; - } + public boolean isAndStep() { + ASTNode keyword = getKeyword(); + return keyword != null && keyword.getElementType() == StoryTokenType.STEP_TYPE_AND; + } - @Override - public PsiReference getReference() { - return new StepPsiReference(this); - } + @Nullable + public ASTNode getKeyword() { + return getNode().findChildByType(StoryTokenType.STEP_TYPES); + } - public StepType getStepType() { - return stepType; - } + public String getStepText() { + int offset = getStepTextOffset(); + if(offset==0) { + return trim(getText()); + } + return trim(getText().substring(offset)); + } - public String getStepText() { - return trim(substringAfter(getText(), " ")); - } + @Nullable + public String getActualStepPrefix() { + ASTNode keyword = getKeyword(); + if (keyword == null) { // that's weird! + return null; + } + return keyword.getText(); + } - public String getActualStepPrefix() { - return substringBefore(getText(), " "); - } + public int getStepTextOffset() { + String stepPrefix = getActualStepPrefix(); + return stepPrefix != null ? stepPrefix.length() : 0; + } } diff --git a/src/com/github/kumaraman21/intellijbehave/parser/StoryElementType.java b/src/com/github/kumaraman21/intellijbehave/parser/StoryElementType.java index 3d41685..489dd72 100644 --- a/src/com/github/kumaraman21/intellijbehave/parser/StoryElementType.java +++ b/src/com/github/kumaraman21/intellijbehave/parser/StoryElementType.java @@ -15,27 +15,36 @@ */ package com.github.kumaraman21.intellijbehave.parser; +import static com.github.kumaraman21.intellijbehave.language.StoryFileType.STORY_FILE_TYPE; + import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.IFileElementType; import com.intellij.psi.tree.TokenSet; + +import org.jbehave.core.steps.StepType; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import static com.github.kumaraman21.intellijbehave.language.StoryFileType.STORY_FILE_TYPE; - public class StoryElementType extends IElementType { + public static final StoryElementType UNKNOWN_FRAGMENT = new StoryElementType("UNKNOWN_FRAGMENT"); + + public static final StoryElementType COMMENT = new StoryElementType("COMMENT"); + public static final IFileElementType STORY_FILE = new IFileElementType(STORY_FILE_TYPE.getLanguage()); + public static final StoryElementType STORY = new StoryElementType("STORY"); + public static final StoryElementType STORY_DESCRIPTION = new StoryElementType("STORY_DESCRIPTION"); + public static final StoryElementType SCENARIO = new StoryElementType("SCENARIO"); + public static final StoryElementType META = new StoryElementType("META"); + public static final StoryElementType EXAMPLES = new StoryElementType("EXAMPLES"); + public static final StoryElementType TABLE_ROW = new StoryElementType("TABLE_ROW"); - public StoryElementType(@NotNull @NonNls String debugName) { - super(debugName, STORY_FILE_TYPE.getLanguage()); - } + public static final StoryElementType GIVEN_STEP = new StoryElementType("GIVEN_STEP"); + public static final StoryElementType WHEN_STEP = new StoryElementType("WHEN_STEP"); + public static final StoryElementType THEN_STEP = new StoryElementType("THEN_STEP"); - public static final IFileElementType STORY_FILE = new IFileElementType(STORY_FILE_TYPE.getLanguage()); - public static final StoryElementType STORY = new StoryElementType("STORY"); - public static final StoryElementType SCENARIO = new StoryElementType("SCENARIO"); + public static final TokenSet STEPS_TOKEN_SET = TokenSet.create(GIVEN_STEP, WHEN_STEP, THEN_STEP); - public static final StoryElementType GIVEN_STEP = new StoryElementType("GIVEN_STEP"); - public static final StoryElementType WHEN_STEP = new StoryElementType("WHEN_STEP"); - public static final StoryElementType THEN_STEP = new StoryElementType("THEN_STEP"); - public static final TokenSet STEPS_TOKEN_SET = TokenSet.create(GIVEN_STEP, WHEN_STEP, THEN_STEP); + public StoryElementType(@NotNull @NonNls String debugName) { + super(debugName, STORY_FILE_TYPE.getLanguage()); + } } diff --git a/src/com/github/kumaraman21/intellijbehave/parser/StoryParser.java b/src/com/github/kumaraman21/intellijbehave/parser/StoryParser.java index 93c81cf..fee7bf4 100644 --- a/src/com/github/kumaraman21/intellijbehave/parser/StoryParser.java +++ b/src/com/github/kumaraman21/intellijbehave/parser/StoryParser.java @@ -16,123 +16,594 @@ package com.github.kumaraman21.intellijbehave.parser; import com.github.kumaraman21.intellijbehave.highlighter.StoryTokenType; +import com.github.kumaraman21.intellijbehave.utility.LocalizedStorySupport; import com.intellij.lang.ASTNode; import com.intellij.lang.PsiBuilder; import com.intellij.lang.PsiParser; import com.intellij.psi.tree.IElementType; -import org.jetbrains.annotations.NotNull; +import com.intellij.psi.tree.TokenSet; -import static com.github.kumaraman21.intellijbehave.utility.StepTypeMappings.STEP_TEXT_TO_STORY_ELEMENT_TYPE_MAPPING; +import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.NotNull; public class StoryParser implements PsiParser { - @NotNull - @Override - public ASTNode parse(IElementType root, PsiBuilder builder) { - final PsiBuilder.Marker rootMarker = builder.mark(); - - parseStory(builder); - rootMarker.done(root); - return builder.getTreeBuilt(); - } - - private void parseStory(PsiBuilder builder) { - final PsiBuilder.Marker storyMarker = builder.mark(); - parseStoryDescriptionLinesIfPresent(builder); - parseScenarios(builder); - storyMarker.done(StoryElementType.STORY); - } - - private void parseStoryDescriptionLinesIfPresent(PsiBuilder builder) { - if(builder.getTokenType() == StoryTokenType.STORY_DESCRIPTION) { - while(builder.getTokenType() == StoryTokenType.STORY_DESCRIPTION) { - parseStoryDescriptionLine(builder); - } - } - } - - private void parseStoryDescriptionLine(PsiBuilder builder) { - builder.advanceLexer(); - } - - private void parseScenarios(PsiBuilder builder) { - if(builder.getTokenType() == StoryTokenType.SCENARIO_TEXT) { - while(builder.getTokenType() == StoryTokenType.SCENARIO_TEXT) { - parseScenario(builder); - } - } - else { - builder.advanceLexer(); - builder.error("Scenario expected"); - } - } - - private void parseScenario(PsiBuilder builder) { - final PsiBuilder.Marker stepMarker = builder.mark(); - builder.advanceLexer(); - parseSteps(builder); - parseStoryDescriptionLinesIfPresent(builder); - stepMarker.done(StoryElementType.SCENARIO); - } - - private void parseSteps(PsiBuilder builder) { - parseStoryDescriptionLinesIfPresent(builder); - if(builder.getTokenType() == StoryTokenType.STEP_TYPE) { - - StoryElementType previousStepElementType = null; - while(builder.getTokenType() == StoryTokenType.STEP_TYPE) { - previousStepElementType = parseStep(builder, previousStepElementType); - parseStoryDescriptionLinesIfPresent(builder); - } - } - else { - builder.error("At least one step expected"); - } - } - - private StoryElementType parseStep(PsiBuilder builder, StoryElementType previousStepElementType) { - final PsiBuilder.Marker stepMarker = builder.mark(); - - StoryElementType currentStepElementType; - - String stepTypeText = builder.getTokenText().trim().toUpperCase(); - if(stepTypeText.equalsIgnoreCase("And")) { - currentStepElementType = previousStepElementType; - } - else { - currentStepElementType = STEP_TEXT_TO_STORY_ELEMENT_TYPE_MAPPING.get(stepTypeText); - } - - parseStepType(builder); - parseStepText(builder); - parseTableIfPresent(builder); - stepMarker.done(currentStepElementType); - - return currentStepElementType; - } - - private void parseStepType(PsiBuilder builder) { - builder.advanceLexer(); - } - - private void parseStepText(PsiBuilder builder) { - if(builder.getTokenType() == StoryTokenType.STEP_TEXT) { - builder.advanceLexer(); - } - else { - builder.error("Step text expected"); - } - } - - private void parseTableIfPresent(PsiBuilder builder) { - if(builder.getTokenType() == StoryTokenType.TABLE_ROW) { - while(builder.getTokenType() == StoryTokenType.TABLE_ROW) { - parseTableRow(builder); - } - } - } - - private void parseTableRow(PsiBuilder builder) { - builder.advanceLexer(); - } + private static final boolean DEBUG = false; + + @NotNull + @Override + public ASTNode parse(IElementType root, PsiBuilder builder) { + final PsiBuilder.Marker rootMarker = builder.mark(); + builder.setDebugMode(true); + parseStory(builder); + rootMarker.done(root); + return builder.getTreeBuilt(); + } + + @SuppressWarnings("UnnecessaryLabelOnContinueStatement") + private void parseStory(PsiBuilder builder) { + final PsiBuilder.Marker storyMarker = builder.mark(); + + ParserState state = new ParserState(builder); + + whileLoop: + while (!builder.eof()) { + IElementType tokenType = builder.getTokenType(); + + // Comment and whitespace are not returned by default + + if (isComment(tokenType)) { + state.enterComment(); + builder.advanceLexer(); + continue whileLoop; + } + else { + state.leaveComment(); + } + + if (isWhitespace(tokenType)) { + if(isCrlf(builder.getTokenText())) { + // this is never called unfortunately + state.leaveTableRow(); + } + + state.enterWhitespace(); + builder.advanceLexer(); + continue whileLoop; + } + else { + state.leaveWhitespace(); + } + + + if (isStoryDescription(tokenType)) { + state.enterStoryDescription(); + builder.advanceLexer(); + continue whileLoop; + } + else { + state.leaveStoryDescription(); + } + + if (isScenario(tokenType)) { + state.enterScenario(); + builder.advanceLexer(); + continue whileLoop; + } + else if(!belongsToScenario(tokenType)) { + state.leaveScenario(); + } + + if (isScenarioText(tokenType)) { + builder.advanceLexer(); + continue whileLoop; + } + + if(isMeta(tokenType)) { + state.enterMeta(); + builder.advanceLexer(); + continue whileLoop; + } + + if(isStepType(tokenType)) { + state.enterStepType(tokenType); + builder.advanceLexer(); + continue whileLoop; + } + + if(isStepText(tokenType)) { + builder.advanceLexer(); + continue whileLoop; + } + + if(isExampleTable(tokenType)) { + state.enterExampleTable(); + builder.advanceLexer(); + continue whileLoop; + } + else if(!belongsToTable(tokenType)) { + state.leaveExampleTable(); + } + + if(isTableRow(tokenType)) { + state.enterTableRow(); + builder.advanceLexer(); + continue whileLoop; + } + + // unknown + PsiBuilder.Marker unknwonMark = builder.mark(); + builder.advanceLexer(); + unknwonMark.done(StoryElementType.UNKNOWN_FRAGMENT); + } + state.leaveRemainings(); + storyMarker.done(StoryElementType.STORY); + } + + private static boolean isCrlf(String text) { + return text.contains("\n") || text.contains("\r"); + } + + private static class MarkerData { + private final PsiBuilder.Marker marker; + private final IElementType elementType; + + private MarkerData(PsiBuilder.Marker marker, IElementType elementType) { + this.marker = marker; + this.elementType = elementType; + } + public boolean matches(IElementType elementType) { + return this.elementType == elementType; + } + public boolean matches(TokenSet tokenSet) { + return tokenSet.contains(this.elementType); + } + public void applyMark() { + marker.done(elementType); + } + } + + private static class ParserState { + private final PsiBuilder builder; + private final MarkerData[] markers = new MarkerData[10]; + private int markerIndex = -1; + private StoryElementType previousStepElementType = null; + + + public ParserState(PsiBuilder builder) { + this.builder = builder; + } + + private void matchesHeadOrPush(StoryElementType elementType) { + if(markerIndex>=0 && markers[markerIndex].matches(elementType)) { + return; + } + markers[++markerIndex] = new MarkerData(builder.mark(), elementType); + if(DEBUG) System.out.println("StoryParser$ParserState: PUSH>> " + StringUtils.repeat("..", markerIndex) + elementType); + } + + private void popUntilOnlyIfPresent(StoryElementType elementType) { + int newMarkerIndex = markerIndex; + for(int i=markerIndex; i>=0; i--) { + if(markers[i].matches(elementType)) { + newMarkerIndex = i - 1; + break; + } + } + + for(int i=newMarkerIndex+1;i<=markerIndex;i++) { + if(DEBUG) System.out.println("StoryParser$ParserState: POP >> " + StringUtils.repeat("..", i) + markers[i].elementType); + markers[i].applyMark(); + } + + markerIndex = newMarkerIndex; + } + + public void leaveRemainings() { + while(markerIndex>=0) { + if(DEBUG) System.out.println("StoryParser$ParserState: POP >> " + StringUtils.repeat("..", markerIndex) + markers[markerIndex].elementType); + markers[markerIndex--].applyMark(); + } + } + + + private void popUntilOnlyIfPresent(TokenSet tokenSet) { + int newMarkerIndex = markerIndex; + for(int i=markerIndex; i>=0; i--) { + if(markers[i].matches(tokenSet)) { + newMarkerIndex = i - 1; + break; + } + } + + for(int i=newMarkerIndex+1;i<=markerIndex;i++) { + System.out.println("StoryParser$ParserState: POP >> " + StringUtils.repeat("..", i) + markers[i].elementType); + markers[i].applyMark(); + } + + markerIndex = newMarkerIndex; + } + + + public void enterComment() { + matchesHeadOrPush(StoryElementType.COMMENT); + } + + private void leaveComment() { + popUntilOnlyIfPresent(StoryElementType.COMMENT); + } + + public void enterWhitespace() { + } + + public void leaveWhitespace() { + } + + public void enterStoryDescription() { + leaveRemainings(); + matchesHeadOrPush(StoryElementType.STORY_DESCRIPTION); + } + + private void leaveStoryDescription() { + popUntilOnlyIfPresent(StoryElementType.STORY_DESCRIPTION); + } + + public void enterScenario() { + leaveRemainings(); + previousStepElementType = null; + matchesHeadOrPush(StoryElementType.SCENARIO); + } + + private void leaveScenario() { + leaveRemainings(); + previousStepElementType = null; + popUntilOnlyIfPresent(StoryElementType.SCENARIO); + } + + public void enterMeta() { + leaveStoryDescription(); + leaveExampleTable(); + leaveTableRow(); + leaveStep(); + matchesHeadOrPush(StoryElementType.META); + } + + private void leaveMeta() { + popUntilOnlyIfPresent(StoryElementType.META); + } + + public void enterStepType(IElementType tokenType) { + leaveExampleTable(); + leaveMeta(); + leaveStep(); + leaveStoryDescription(); + leaveTableRow(); + + StoryElementType elementType = previousStepElementType; + if (tokenType == StoryTokenType.STEP_TYPE_GIVEN) { + elementType = StoryElementType.GIVEN_STEP; + } + else if (tokenType == StoryTokenType.STEP_TYPE_WHEN) { + elementType = StoryElementType.WHEN_STEP; + } + else if (tokenType == StoryTokenType.STEP_TYPE_THEN) { + elementType = StoryElementType.THEN_STEP; + } + else { + // should be the AND + if (tokenType != StoryTokenType.STEP_TYPE_AND) { + // throw... + } + } + + if (elementType == null) { // yuk! + elementType = StoryElementType.GIVEN_STEP; + } + previousStepElementType = elementType; + matchesHeadOrPush(elementType); + } + + private void leaveStep() { + leaveTableRow(); + popUntilOnlyIfPresent(StoryElementType.STEPS_TOKEN_SET); + } + + public void enterExampleTable() { + leaveTableRow(); + leaveStep(); + leaveMeta(); + leaveExampleTable(); + matchesHeadOrPush(StoryElementType.EXAMPLES); + } + + public void leaveExampleTable() { + leaveTableRow(); + popUntilOnlyIfPresent(StoryElementType.EXAMPLES); + } + + public void enterTableRow() { + matchesHeadOrPush(StoryElementType.TABLE_ROW); + } + + public void leaveTableRow() { + popUntilOnlyIfPresent(StoryElementType.TABLE_ROW); + } + } + + private static boolean belongsToScenario(IElementType tokenType) { + return isWhitespace(tokenType) + || isComment(tokenType) + || isScenarioText(tokenType) + || isStoryDescription(tokenType) + || isStepType(tokenType) + || isStepText(tokenType) + || isExampleTable(tokenType) + || isTableRow(tokenType) + || isMeta(tokenType); + } + + private boolean belongsToTable(IElementType tokenType) { + return isWhitespace(tokenType) + || isComment(tokenType) + || isTableRow(tokenType); + } + private static boolean isMeta(IElementType tokenType) { + return tokenType == StoryTokenType.META + || tokenType == StoryTokenType.META_KEY + || tokenType == StoryTokenType.META_TEXT + ; + } + + private static boolean isGivenStories(IElementType tokenType) { + return tokenType == StoryTokenType.GIVEN_STORIES; + } + + private static boolean isExampleTable(IElementType tokenType) { + return tokenType == StoryTokenType.EXAMPLE_TYPE; + } + + private static boolean isTableRow(IElementType tokenType) { + return tokenType == StoryTokenType.TABLE_CELL + || tokenType == StoryTokenType.TABLE_DELIM; + } + + private static boolean isStepType(IElementType tokenType) { + return tokenType == StoryTokenType.STEP_TYPE + || tokenType == StoryTokenType.STEP_TYPE_GIVEN + || tokenType == StoryTokenType.STEP_TYPE_WHEN + || tokenType == StoryTokenType.STEP_TYPE_THEN + || tokenType == StoryTokenType.STEP_TYPE_AND; + } + + private static boolean isStepText(IElementType tokenType) { + return tokenType == StoryTokenType.STEP_TEXT; + } + + private static boolean isScenario(IElementType tokenType) { + return tokenType == StoryTokenType.SCENARIO_TYPE; + } + + private static boolean isScenarioText(IElementType tokenType) { + return tokenType == StoryTokenType.SCENARIO_TEXT; + } + + private static boolean isWhitespace(IElementType tokenType) { + return tokenType == StoryTokenType.WHITE_SPACE; + } + + private static boolean isComment(IElementType tokenType) { + return tokenType == StoryTokenType.COMMENT + || tokenType == StoryTokenType.COMMENT_WITH_LOCALE; + } + + private static boolean isStoryDescription(IElementType tokenType) { + return tokenType == StoryTokenType.STORY_DESCRIPTION + || tokenType == StoryTokenType.NARRATIVE_TYPE + || tokenType == StoryTokenType.NARRATIVE_TEXT + ; + } + + private void skipWhitespacesOrComments(PsiBuilder builder) { + skipWhitespacesOrComments(builder, false); + } + + private String skipWhitespacesOrComments(PsiBuilder builder, boolean checkForLocale) { + String storyLocale = "en"; + + while (isWhitespace(builder.getTokenType()) + || builder.getTokenType() == StoryTokenType.COMMENT) + { + if (builder.getTokenType() == StoryTokenType.COMMENT) { + PsiBuilder.Marker commentMark = builder.mark(); + while (builder.getTokenType() == StoryTokenType.COMMENT) { + if (checkForLocale) { + String commentText = builder.getTokenText(); + String locale = LocalizedStorySupport.checkForLanguageDefinition(commentText); + if (locale != null) { + storyLocale = locale; + } + } + builder.advanceLexer(); + } + commentMark.done(StoryElementType.COMMENT); + } + else { + builder.advanceLexer(); + } + } + + return storyLocale; + } + + private void parseStoryDescriptionOrNarrativesLinesIfPresent(PsiBuilder builder) { + if (builder.getTokenType() != StoryTokenType.STORY_DESCRIPTION + && builder.getTokenType() != StoryTokenType.NARRATIVE_TYPE + && builder.getTokenType() != StoryTokenType.NARRATIVE_TEXT) + { + return; + } + + PsiBuilder.Marker marker = builder.mark(); + while (builder.getTokenType() == StoryTokenType.STORY_DESCRIPTION + || builder.getTokenType() == StoryTokenType.NARRATIVE_TYPE + || builder.getTokenType() == StoryTokenType.NARRATIVE_TEXT) + { + parseStoryDescriptionLine(builder); + skipWhitespacesOrComments(builder); + } + marker.done(StoryElementType.STORY_DESCRIPTION); + } + + private void parseStoryDescriptionLine(PsiBuilder builder) { + builder.advanceLexer(); + } + + private void parseScenarios(PsiBuilder builder) { + if (builder.getTokenType() == StoryTokenType.SCENARIO_TYPE) { + while (builder.getTokenType() == StoryTokenType.SCENARIO_TYPE) { + parseScenario(builder); + skipWhitespacesOrComments(builder); + } + } + else { + builder.advanceLexer(); + builder.error("Scenario expected"); + } + } + + private void parseScenario(PsiBuilder builder) { + final PsiBuilder.Marker stepMarker = builder.mark(); + builder.advanceLexer(); + skipWhitespacesOrComments(builder); + while (builder.getTokenType() == StoryTokenType.SCENARIO_TEXT) { + parseScenarioText(builder); + skipWhitespacesOrComments(builder); + } + parseMeta(builder); + parseSteps(builder); + skipWhitespacesOrComments(builder); + parseStoryDescriptionOrNarrativesLinesIfPresent(builder); + skipWhitespacesOrComments(builder); + parseExamples(builder); + stepMarker.done(StoryElementType.SCENARIO); + } + + private void parseScenarioText(PsiBuilder builder) { + builder.advanceLexer(); + } + + private void parseMeta(PsiBuilder builder) { + if (builder.getTokenType() == StoryTokenType.META) { + final PsiBuilder.Marker stepMarker = builder.mark(); + while (builder.getTokenType() == StoryTokenType.META + || builder.getTokenType() == StoryTokenType.META_TEXT + || builder.getTokenType() == StoryTokenType.META_KEY) + { + builder.advanceLexer(); + skipWhitespacesOrComments(builder); + } + stepMarker.done(StoryElementType.META); + } + } + + private void parseSteps(PsiBuilder builder) { + parseStoryDescriptionOrNarrativesLinesIfPresent(builder); + if (isStepType(builder.getTokenType())) { + StoryElementType previousStepElementType = null; + while (isStepType(builder.getTokenType())) { + previousStepElementType = parseStep(builder, previousStepElementType); + skipWhitespacesOrComments(builder); + parseStoryDescriptionOrNarrativesLinesIfPresent(builder); + } + } + else { + builder.error("At least one step expected"); + } + } + + + private StoryElementType parseStep(PsiBuilder builder, StoryElementType previousStepElementType) { + final PsiBuilder.Marker stepMarker = builder.mark(); + StoryElementType currentStepElementType; + + // TODO find a nicer way to perform the switch + IElementType tokenType = builder.getTokenType(); + if (tokenType == StoryTokenType.STEP_TYPE_GIVEN) { + currentStepElementType = StoryElementType.GIVEN_STEP; + } + else if (tokenType == StoryTokenType.STEP_TYPE_WHEN) { + currentStepElementType = StoryElementType.WHEN_STEP; + } + else if (tokenType == StoryTokenType.STEP_TYPE_THEN) { + currentStepElementType = StoryElementType.THEN_STEP; + } + else { + // should be the AND + if (tokenType != StoryTokenType.STEP_TYPE_AND) { + // throw... + } + currentStepElementType = previousStepElementType; + } + + if (currentStepElementType == null) { + // yuk! + currentStepElementType = StoryElementType.GIVEN_STEP; + } + + parseStepType(builder); + parseStepText(builder); + skipWhitespacesOrComments(builder); + parseTableIfPresent(builder); + skipWhitespacesOrComments(builder); + stepMarker.done(currentStepElementType); + + return currentStepElementType; + } + + private void parseStepType(PsiBuilder builder) { + builder.advanceLexer(); + } + + private void parseStepText(PsiBuilder builder) { + if (builder.getTokenType() == StoryTokenType.STEP_TEXT) { + while (builder.getTokenType() == StoryTokenType.STEP_TEXT) { + builder.advanceLexer(); + skipWhitespacesOrComments(builder); + } + } + else { + builder.error("Step text expected"); + } + } + + private void parseExamples(PsiBuilder builder) { + if (builder.getTokenType() == StoryTokenType.EXAMPLE_TYPE) { + final PsiBuilder.Marker stepMarker = builder.mark(); + builder.advanceLexer(); + skipWhitespacesOrComments(builder); + if (builder.getTokenType() == StoryTokenType.TABLE_DELIM) { + parseTableIfPresent(builder); + } + else { + builder.error("Table row expected"); + } + + stepMarker.done(StoryElementType.EXAMPLES); + } + } + + private void parseTableIfPresent(PsiBuilder builder) { + if (builder.getTokenType() == StoryTokenType.TABLE_DELIM) { + while (builder.getTokenType() == StoryTokenType.TABLE_DELIM + || builder.getTokenType() == StoryTokenType.TABLE_CELL) + { + parseTableToken(builder); + skipWhitespacesOrComments(builder); + } + } + } + + private void parseTableToken(PsiBuilder builder) { + builder.advanceLexer(); + } } diff --git a/src/com/github/kumaraman21/intellijbehave/parser/StoryParserDefinition.java b/src/com/github/kumaraman21/intellijbehave/parser/StoryParserDefinition.java index 2227a14..68d89f2 100644 --- a/src/com/github/kumaraman21/intellijbehave/parser/StoryParserDefinition.java +++ b/src/com/github/kumaraman21/intellijbehave/parser/StoryParserDefinition.java @@ -15,7 +15,7 @@ */ package com.github.kumaraman21.intellijbehave.parser; -import com.github.kumaraman21.intellijbehave.highlighter.StoryLexer; +import com.github.kumaraman21.intellijbehave.highlighter.StoryLexerFactory; import com.github.kumaraman21.intellijbehave.highlighter.StoryTokenType; import com.intellij.extapi.psi.ASTWrapperPsiElement; import com.intellij.lang.ASTNode; @@ -29,15 +29,15 @@ import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.IFileElementType; import com.intellij.psi.tree.TokenSet; -import org.jetbrains.annotations.NotNull; -import static com.github.kumaraman21.intellijbehave.utility.StepTypeMappings.STORY_ELEMENT_TYPE_TO_STEP_TYPE_MAPPING; +import org.jbehave.core.steps.StepType; +import org.jetbrains.annotations.NotNull; public class StoryParserDefinition implements ParserDefinition { @NotNull @Override public Lexer createLexer(Project project) { - return new StoryLexer(); + return new StoryLexerFactory().createLexer(); } @Override @@ -59,7 +59,7 @@ public TokenSet getWhitespaceTokens() { @NotNull @Override public TokenSet getCommentTokens() { - return TokenSet.create(StoryTokenType.COMMENT); + return TokenSet.create(StoryTokenType.COMMENT, StoryTokenType.COMMENT_WITH_LOCALE); } @NotNull @@ -72,8 +72,14 @@ public TokenSet getStringLiteralElements() { @Override public PsiElement createElement(ASTNode node) { final IElementType type = node.getElementType(); - if (type == StoryElementType.GIVEN_STEP || type == StoryElementType.WHEN_STEP || type == StoryElementType.THEN_STEP) { - return new StepPsiElement(node, STORY_ELEMENT_TYPE_TO_STEP_TYPE_MAPPING.get(type)); + if (type == StoryElementType.GIVEN_STEP) { + return new StepPsiElement(node, StepType.GIVEN); + } + else if (type == StoryElementType.WHEN_STEP) { + return new StepPsiElement(node, StepType.WHEN); + } + else if (type == StoryElementType.THEN_STEP) { + return new StepPsiElement(node, StepType.THEN); } return new ASTWrapperPsiElement(node); diff --git a/src/com/github/kumaraman21/intellijbehave/resolver/StepDefinitionAnnotation.java b/src/com/github/kumaraman21/intellijbehave/resolver/StepDefinitionAnnotation.java index 635fd4e..b85e53c 100644 --- a/src/com/github/kumaraman21/intellijbehave/resolver/StepDefinitionAnnotation.java +++ b/src/com/github/kumaraman21/intellijbehave/resolver/StepDefinitionAnnotation.java @@ -19,9 +19,9 @@ import org.jbehave.core.steps.StepType; public class StepDefinitionAnnotation { - private StepType stepType; - private String annotationText; - private PsiAnnotation annotation; + private final StepType stepType; + private final String annotationText; + private final PsiAnnotation annotation; public StepDefinitionAnnotation(StepType stepType, String annotationText, PsiAnnotation annotation) { this.stepType = stepType; diff --git a/src/com/github/kumaraman21/intellijbehave/resolver/StepDefinitionAnnotationConverter.java b/src/com/github/kumaraman21/intellijbehave/resolver/StepDefinitionAnnotationConverter.java index d39f3ac..ceff21d 100644 --- a/src/com/github/kumaraman21/intellijbehave/resolver/StepDefinitionAnnotationConverter.java +++ b/src/com/github/kumaraman21/intellijbehave/resolver/StepDefinitionAnnotationConverter.java @@ -20,8 +20,10 @@ import com.intellij.psi.PsiLiteral; import org.jbehave.core.annotations.Alias; import org.jbehave.core.annotations.Aliases; +import org.jbehave.core.steps.PatternVariantBuilder; import org.jbehave.core.steps.StepType; +import java.util.List; import java.util.Set; import static com.github.kumaraman21.intellijbehave.utility.StepTypeMappings.ANNOTATION_TO_STEP_TYPE_MAPPING; @@ -37,27 +39,32 @@ public Set convertFrom(PsiAnnotation[] annotations) { for (PsiAnnotation annotation : annotations) { // Given, When, Then - if(ANNOTATION_TO_STEP_TYPE_MAPPING.keySet().contains(annotation.getQualifiedName())) { + if (ANNOTATION_TO_STEP_TYPE_MAPPING.keySet().contains(annotation.getQualifiedName())) { stepType = ANNOTATION_TO_STEP_TYPE_MAPPING.get(annotation.getQualifiedName()); String annotationText = getTextFromValue(annotation.getParameterList().getAttributes()[0].getValue()); - stepDefinitionAnnotations.add(new StepDefinitionAnnotation(stepType, annotationText, annotation)); - } - else if(annotation.getQualifiedName().equals(Alias.class.getName())) { + stepDefinitionAnnotations.addAll(getPatternVariants(stepType, annotationText, annotation)); + } else if (annotation.getQualifiedName().equals(Alias.class.getName())) { String annotationText = getTextFromValue(annotation.getParameterList().getAttributes()[0].getValue()); - stepDefinitionAnnotations.add(new StepDefinitionAnnotation(stepType, annotationText, annotation)); - } - else if(annotation.getQualifiedName().equals(Aliases.class.getName())) { - + stepDefinitionAnnotations.addAll(getPatternVariants(stepType, annotationText, annotation)); + } else if (annotation.getQualifiedName().equals(Aliases.class.getName())) { PsiElement[] values = annotation.getParameterList().getAttributes()[0].getValue().getChildren(); for (PsiElement value : values) { - if(value instanceof PsiLiteral) { + if (value instanceof PsiLiteral) { String annotationText = getTextFromValue(value); - stepDefinitionAnnotations.add(new StepDefinitionAnnotation(stepType, annotationText, annotation)); + stepDefinitionAnnotations.addAll(getPatternVariants(stepType, annotationText, annotation)); } } } } + return stepDefinitionAnnotations; + } + private Set getPatternVariants(StepType stepType, String annotationText, PsiAnnotation annotation) { + Set stepDefinitionAnnotations = newHashSet(); + PatternVariantBuilder b = new PatternVariantBuilder(annotationText); + for (String variant : b.allVariants()) { + stepDefinitionAnnotations.add(new StepDefinitionAnnotation(stepType, variant, annotation)); + } return stepDefinitionAnnotations; } diff --git a/src/com/github/kumaraman21/intellijbehave/resolver/StepDefinitionIterator.java b/src/com/github/kumaraman21/intellijbehave/resolver/StepDefinitionIterator.java index 561c700..44f2b34 100644 --- a/src/com/github/kumaraman21/intellijbehave/resolver/StepDefinitionIterator.java +++ b/src/com/github/kumaraman21/intellijbehave/resolver/StepDefinitionIterator.java @@ -19,6 +19,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import org.jbehave.core.steps.StepType; +import org.jetbrains.annotations.Nullable; import java.util.Set; @@ -27,9 +28,9 @@ public abstract class StepDefinitionIterator implements ContentIterator { private final StepDefinitionAnnotationConverter stepDefinitionAnnotationConverter = new StepDefinitionAnnotationConverter(); - private StepType stepType; + private final StepType stepType; - public StepDefinitionIterator(StepType stepType) { + public StepDefinitionIterator(@Nullable StepType stepType) { this.stepType = stepType; } @@ -48,10 +49,10 @@ public boolean processFile(VirtualFile virtualFile) { Set stepDefinitionAnnotations = stepDefinitionAnnotationConverter.convertFrom(annotations); for (StepDefinitionAnnotation stepDefinitionAnnotation : stepDefinitionAnnotations) { - if(stepDefinitionAnnotation.getStepType().equals(stepType)) { + if(stepType==null || stepDefinitionAnnotation.getStepType().equals(stepType)) { boolean shouldContinue = processStepDefinition(stepDefinitionAnnotation); - if(shouldContinue == false) { + if(!shouldContinue) { return shouldContinue; } } diff --git a/src/com/github/kumaraman21/intellijbehave/resolver/StepPsiReference.java b/src/com/github/kumaraman21/intellijbehave/resolver/StepPsiReference.java index c7cc8f8..f4beb66 100644 --- a/src/com/github/kumaraman21/intellijbehave/resolver/StepPsiReference.java +++ b/src/com/github/kumaraman21/intellijbehave/resolver/StepPsiReference.java @@ -15,139 +15,155 @@ */ package com.github.kumaraman21.intellijbehave.resolver; +import static com.github.kumaraman21.intellijbehave.utility.ProjectUtils.getProjectFileIndex; +import static com.google.common.collect.Lists.newArrayList; +import static org.apache.commons.lang.StringUtils.trim; + import com.github.kumaraman21.intellijbehave.parser.StepPsiElement; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; import com.intellij.util.IncorrectOperationException; + import org.jbehave.core.parsers.RegexPrefixCapturingPatternParser; import org.jbehave.core.parsers.StepMatcher; import org.jbehave.core.parsers.StepPatternParser; import org.jbehave.core.steps.StepType; import org.jetbrains.annotations.NotNull; - import java.util.List; -import static com.github.kumaraman21.intellijbehave.utility.ProjectUtils.getProjectFileIndex; -import static com.google.common.collect.Lists.newArrayList; -import static org.apache.commons.lang.StringUtils.trim; - public class StepPsiReference implements PsiReference { - private StepPsiElement stepPsiElement; + private StepPsiElement stepPsiElement; - public StepPsiReference(StepPsiElement stepPsiElement) { - this.stepPsiElement = stepPsiElement; - } + public StepPsiReference(StepPsiElement stepPsiElement) { + this.stepPsiElement = stepPsiElement; + } - @Override - public PsiElement getElement() { - return stepPsiElement; - } + @Override + public PsiElement getElement() { + return stepPsiElement; + } - @Override - public TextRange getRangeInElement() { - return TextRange.from(0, stepPsiElement.getTextLength()); - } + @Override + public TextRange getRangeInElement() { + return TextRange.from(0, stepPsiElement.getTextLength()); + } - @Override - public PsiElement resolve() { - StepType stepType = stepPsiElement.getStepType(); - String stepText = stepPsiElement.getStepText(); + public StepDefinitionAnnotation stepDefinitionAnnotation() { + StepType stepType = stepPsiElement.getStepType(); + String stepText = stepPsiElement.getStepText(); - StepAnnotationFinder stepAnnotationFinder = new StepAnnotationFinder(stepType, stepText); - getProjectFileIndex().iterateContent(stepAnnotationFinder); + StepAnnotationFinder stepAnnotationFinder = new StepAnnotationFinder(stepType, stepText); + getProjectFileIndex().iterateContent(stepAnnotationFinder); - return stepAnnotationFinder.getMatchingAnnotation(); - } + return stepAnnotationFinder.getMatchingAnnotation(); + } - @NotNull - @Override - public Object[] getVariants() { - StepType stepType = stepPsiElement.getStepType(); - String actualStepPrefix = stepPsiElement.getActualStepPrefix(); + @Override + public PsiElement resolve() { + StepDefinitionAnnotation stepDefinitionAnnotation = stepDefinitionAnnotation(); + if(stepDefinitionAnnotation==null) + return null; + return stepDefinitionAnnotation.getAnnotation(); + } - StepSuggester stepSuggester = new StepSuggester(stepType, actualStepPrefix); - getProjectFileIndex().iterateContent(stepSuggester); + private static final boolean useVariants = false; - return stepSuggester.getSuggestions().toArray(); - } + @NotNull + @Override + public Object[] getVariants() { - private static class StepSuggester extends StepDefinitionIterator { + if (useVariants) { + StepType stepType = stepPsiElement.getStepType(); + String actualStepPrefix = stepPsiElement.getActualStepPrefix(); - private List suggestions = newArrayList(); - private String actualStepPrefix; + StepSuggester stepSuggester = new StepSuggester(stepType, actualStepPrefix); + getProjectFileIndex().iterateContent(stepSuggester); - public StepSuggester(StepType stepType, String actualStepPrefix) { - super(stepType); - this.actualStepPrefix = actualStepPrefix; + return stepSuggester.getSuggestions().toArray(); + } + else { + return new Object[0]; + } } - @Override - public boolean processStepDefinition(StepDefinitionAnnotation stepDefinitionAnnotation) { - suggestions.add(actualStepPrefix + " " + stepDefinitionAnnotation.getAnnotationText()); - return true; - } + private static class StepSuggester extends StepDefinitionIterator { + + private final List suggestions = newArrayList(); + private final String actualStepPrefix; + + public StepSuggester(StepType stepType, String actualStepPrefix) { + super(stepType); + this.actualStepPrefix = actualStepPrefix; + } - public List getSuggestions() { - return suggestions; + @Override + public boolean processStepDefinition(StepDefinitionAnnotation stepDefinitionAnnotation) { + suggestions.add(actualStepPrefix + " " + stepDefinitionAnnotation.getAnnotationText()); + return true; + } + + public List getSuggestions() { + return suggestions; + } } - } -private static class StepAnnotationFinder extends StepDefinitionIterator { + private static class StepAnnotationFinder extends StepDefinitionIterator { + + private StepType stepType; + private String stepText; + private StepDefinitionAnnotation matchingAnnotation; + private StepPatternParser stepPatternParser = new RegexPrefixCapturingPatternParser(); - private StepType stepType; - private String stepText; - private PsiElement matchingAnnotation; - private StepPatternParser stepPatternParser = new RegexPrefixCapturingPatternParser(); + private StepAnnotationFinder(StepType stepType, String stepText) { + super(stepType); + this.stepType = stepType; + this.stepText = stepText; + } - private StepAnnotationFinder(StepType stepType, String stepText) { - super(stepType); - this.stepType = stepType; - this.stepText = stepText; - } + @Override + public boolean processStepDefinition(StepDefinitionAnnotation stepDefinitionAnnotation) { + StepMatcher stepMatcher = stepPatternParser.parseStep(stepType, stepDefinitionAnnotation.getAnnotationText()); - @Override - public boolean processStepDefinition(StepDefinitionAnnotation stepDefinitionAnnotation) { - StepMatcher stepMatcher = stepPatternParser.parseStep(stepType, stepDefinitionAnnotation.getAnnotationText()); + if (stepMatcher.matches(stepText)) { + matchingAnnotation = stepDefinitionAnnotation; - if(stepMatcher.matches(stepText)) { - matchingAnnotation = stepDefinitionAnnotation.getAnnotation(); + return false; + } + return true; + } - return false; + public StepDefinitionAnnotation getMatchingAnnotation() { + return matchingAnnotation; + } } - return true; - } - public PsiElement getMatchingAnnotation() { - return matchingAnnotation; - } -} + @NotNull + @Override + public String getCanonicalText() { + return trim(stepPsiElement.getText()); + } - @NotNull - @Override - public String getCanonicalText() { - return trim(stepPsiElement.getText()); - } - - @Override - public PsiElement handleElementRename(String s) throws IncorrectOperationException { - throw new IncorrectOperationException(); - } - - @Override - public PsiElement bindToElement(@NotNull PsiElement psiElement) throws IncorrectOperationException { - throw new IncorrectOperationException(); - } - - @Override - public boolean isReferenceTo(PsiElement psiElement) { - return psiElement instanceof StepPsiElement && Comparing.equal(resolve(), psiElement); - } - - @Override - public boolean isSoft() { - return false; - } + @Override + public PsiElement handleElementRename(String s) throws IncorrectOperationException { + throw new IncorrectOperationException(); + } + + @Override + public PsiElement bindToElement(@NotNull PsiElement psiElement) throws IncorrectOperationException { + throw new IncorrectOperationException(); + } + + @Override + public boolean isReferenceTo(PsiElement psiElement) { + return psiElement instanceof StepPsiElement && Comparing.equal(resolve(), psiElement); + } + + @Override + public boolean isSoft() { + return false; + } } diff --git a/src/com/github/kumaraman21/intellijbehave/resolver/StoryAnnotator.java b/src/com/github/kumaraman21/intellijbehave/resolver/StoryAnnotator.java index eb35c6f..b0c7c5b 100644 --- a/src/com/github/kumaraman21/intellijbehave/resolver/StoryAnnotator.java +++ b/src/com/github/kumaraman21/intellijbehave/resolver/StoryAnnotator.java @@ -15,22 +15,54 @@ */ package com.github.kumaraman21.intellijbehave.resolver; +import static com.github.kumaraman21.intellijbehave.utility.ParametrizedString.StringToken; + import com.github.kumaraman21.intellijbehave.parser.StepPsiElement; +import com.github.kumaraman21.intellijbehave.utility.ParametrizedString; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.codeInspection.ProblemHighlightType; +import com.intellij.codeInspection.ex.ProblemDescriptorImpl; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.Annotator; +import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; + import org.jetbrains.annotations.NotNull; public class StoryAnnotator implements Annotator { - @Override - public void annotate(@NotNull PsiElement psiElement, @NotNull AnnotationHolder annotationHolder) { - if(! (psiElement instanceof StepPsiElement)) { - return; + @Override + public void annotate(@NotNull PsiElement psiElement, @NotNull AnnotationHolder annotationHolder) { + if (!(psiElement instanceof StepPsiElement)) { + return; + } + + StepPsiElement stepPsiElement = (StepPsiElement) psiElement; + StepDefinitionAnnotation annotationDef = stepPsiElement.getReference().stepDefinitionAnnotation(); + if (annotationDef == null) { + annotationHolder.createErrorAnnotation(psiElement, "No definition found for the step"); + } + else { + annotateParameters(stepPsiElement, annotationDef, annotationHolder); + } } - StepPsiElement stepPsiElement = (StepPsiElement) psiElement; - if(stepPsiElement.getReference().resolve() == null) { - annotationHolder.createErrorAnnotation(psiElement, "No definition found for the step"); + private void annotateParameters(StepPsiElement stepPsiElement, + StepDefinitionAnnotation annotation, + AnnotationHolder annotationHolder) + { + String stepText = stepPsiElement.getStepText(); + String annotationText = annotation.getAnnotationText(); + ParametrizedString pString = new ParametrizedString(annotationText); + + int offset = stepPsiElement.getTextOffset(); + for (StringToken token : pString.tokenize(stepText)) { + int length = token.getValue().length(); + if (token.isIdentifier()) { + annotationHolder.createInfoAnnotation( + TextRange.from(offset, length), "Parameter"); + } + offset += length; + } } - } } diff --git a/src/com/github/kumaraman21/intellijbehave/template/JBehaveTemplateLoaderComponent.java b/src/com/github/kumaraman21/intellijbehave/template/JBehaveTemplateLoaderComponent.java index f7639a5..1d734ab 100644 --- a/src/com/github/kumaraman21/intellijbehave/template/JBehaveTemplateLoaderComponent.java +++ b/src/com/github/kumaraman21/intellijbehave/template/JBehaveTemplateLoaderComponent.java @@ -21,6 +21,7 @@ import org.jetbrains.annotations.NotNull; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import static com.github.kumaraman21.intellijbehave.language.StoryFileType.STORY_FILE_TYPE; @@ -33,13 +34,24 @@ public void initComponent() { if (template == null) { template = FileTemplateManager.getInstance() .addTemplate(STORY_FILE_TYPE.getName(), STORY_FILE_TYPE.getDefaultExtension()); + + InputStream stream = getClass().getResourceAsStream("/fileTemplates/JBehave Story.story.ft"); try { - template.setText( - loadTextAndClose(new InputStreamReader(getClass().getResourceAsStream("/fileTemplates/JBehave Story.story.ft")))); + if(stream!=null) + template.setText(loadTextAndClose(new InputStreamReader(stream))); } catch (IOException e) { e.printStackTrace(); } + finally { + try { + if(stream!=null) + stream.close(); + } + catch (IOException e) { + e.printStackTrace(); + } + } } } diff --git a/src/com/github/kumaraman21/intellijbehave/utility/CharTree.java b/src/com/github/kumaraman21/intellijbehave/utility/CharTree.java new file mode 100644 index 0000000..b5e7d28 --- /dev/null +++ b/src/com/github/kumaraman21/intellijbehave/utility/CharTree.java @@ -0,0 +1,152 @@ +package com.github.kumaraman21.intellijbehave.utility; + +import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author @aloyer + */ +public class CharTree { + private Map> children = new HashMap>(); + private final int key; + private T value; + + public CharTree(int key) { + this(key, null); + } + + public CharTree(int key, @Nullable T value) { + super(); + this.key = key; + this.value = value; + } + + public T lookupValue(CharSequence seq) { + return lookupValue(seq, 0); + } + + public T lookupValue(CharSequence seq, int offset) { + return lookup(seq, offset).value; + } + + public Entry lookup(CharSequence seq, int offset) { + CharTree ct = this; + T found = this.value; + + int i = offset; + for (int n = seq.length(); i < n; i++) { + ct = ct.get(seq.charAt(i)); + if (ct == null) { + break; + } + if (ct.value != null) { + found = ct.value; + } + } + return new Entry(found, i - offset); + } + + public void print(int level) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < level; i++) { + builder.append(" | "); + } + System.out.print(builder.toString()); + System.out.print("["); + System.out.print((char) key); + System.out.print("] "); + System.out.println(value == null ? "n/a" : value); + + List keys = new ArrayList(children.keySet()); + Collections.sort(keys); + for (Integer key : keys) { + children.get(key).print(level + 1); + } + } + + public void push(CharSequence seq, T value) { + push(seq, 0, value); + } + + private void push(CharSequence seq, int pos, T value) { + if (pos < seq.length()) { + int c = seq.charAt(pos); + CharTree child = getOrCreate(c); + child.push(seq, pos + 1, value); + } + else { + this.value = value; + } + } + + private CharTree get(int c) { + return children.get(c); + } + + private CharTree getOrCreate(int c) { + CharTree cn = children.get(c); + if (cn == null) { + cn = new CharTree(c); + children.put(c, cn); + } + return cn; + } + + public boolean isLeaf() { + return children.isEmpty(); + } + + public static class Entry { + public final T value; + public final int length; + + public Entry(T value, int length) { + this.value = value; + this.length = length; + } + + public boolean hasValue() { + return value != null; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Entry entry = (Entry) o; + + if (length != entry.length) { + return false; + } + if (value != null ? !value.equals(entry.value) : entry.value != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = value != null ? value.hashCode() : 0; + result = 31 * result + length; + return result; + } + + @Override + public String toString() { + return "Entry{" + + "value=" + value + + ", length=" + length + + '}'; + } + } +} diff --git a/src/com/github/kumaraman21/intellijbehave/utility/JBKeyword.java b/src/com/github/kumaraman21/intellijbehave/utility/JBKeyword.java new file mode 100644 index 0000000..e27eb91 --- /dev/null +++ b/src/com/github/kumaraman21/intellijbehave/utility/JBKeyword.java @@ -0,0 +1,244 @@ +package com.github.kumaraman21.intellijbehave.utility; + +import org.jbehave.core.configuration.Keywords; +import org.jbehave.core.steps.StepType; + +/** + * @author @aloyer + */ + +public enum JBKeyword { + Meta + { + @Override + public String asString(Keywords keywords) { + return keywords.meta(); + } + }, + MetaProperty + { + @Override + public String asString(Keywords keywords) { + return keywords.metaProperty(); + } + }, + Narrative + { + @Override + public String asString(Keywords keywords) { + return keywords.narrative(); + } + }, + InOrderTo + { + @Override + public String asString(Keywords keywords) { + return keywords.inOrderTo(); + } + }, + AsA + { + @Override + public String asString(Keywords keywords) { + return keywords.asA(); + } + }, + IWantTo + { + @Override + public String asString(Keywords keywords) { + return keywords.iWantTo(); + } + }, + Scenario + { + @Override + public String asString(Keywords keywords) { + return keywords.scenario(); + } + }, + GivenStories + { + @Override + public String asString(Keywords keywords) { + return keywords.givenStories(); + } + }, + ExamplesTable + { + @Override + public String asString(Keywords keywords) { + return keywords.examplesTable(); + } + }, + ExamplesTableRow + { + @Override + public String asString(Keywords keywords) { + return keywords.examplesTableRow(); + } + }, + ExamplesTableHeaderSeparator + { + @Override + public String asString(Keywords keywords) { + return keywords.examplesTableHeaderSeparator(); + } + }, + ExamplesTableValueSeparator + { + @Override + public String asString(Keywords keywords) { + return keywords.examplesTableValueSeparator(); + } + }, + ExamplesTableIgnorableSeparator + { + @Override + public String asString(Keywords keywords) { + return keywords.examplesTableIgnorableSeparator(); + } + }, + Given + { + @Override + public String asString(Keywords keywords) { + return keywords.given(); + } + }, + When + { + @Override + public String asString(Keywords keywords) { + return keywords.when(); + } + }, + Then + { + @Override + public String asString(Keywords keywords) { + return keywords.then(); + } + }, + And + { + @Override + public String asString(Keywords keywords) { + return keywords.and(); + } + }, + Ignorable + { + @Override + public String asString(Keywords keywords) { + return keywords.ignorable(); + } + }; + + private static Keywords keywords = new Keywords(); + + public String asString() { + return asString(keywords); + } + + public String asString(Keywords keywords) { + throw new AbstractMethodError(); + } + + public boolean isStep() { + return isStep(this); + } + + public boolean isNarrative() { + return isNarrative(this); + } + + public boolean isExampleTable() { + return isExampleTable(this); + } + + public boolean isComment() { + return this == Ignorable; + } + + public static JBKeyword lookup(StringBuilder builder, Keywords keywords) { + int len = builder.length(); + for (JBKeyword jk : values()) { + String asString = jk.asString(keywords); + if (asString.length() != builder.length()) { + continue; + } + + boolean match = true; + for (int i = 0; i < len && match; i++) { + if (builder.charAt(i) != asString.charAt(i)) { + match = false; + } + } + if (match) { + return jk; + } + } + return null; + } + + public static boolean isStep(JBKeyword keyword) { + if (keyword == null) { + return false; + } + switch (keyword) { + case Given: + case When: + case Then: + case And: + return true; + default: + return false; + } + } + + public boolean isSameAs(StepType type) { + switch (type) { + case GIVEN: + return this == Given; + case WHEN: + return this == When; + case THEN: + return this == Then; + default: + return false; + } + } + + public static boolean isNarrative(JBKeyword keyword) { + if (keyword == null) { + return false; + } + switch (keyword) { + case Narrative: + case InOrderTo: + case AsA: + case IWantTo: + return true; + default: + return false; + } + } + + public static boolean isExampleTable(JBKeyword keyword) { + if (keyword == null) { + return false; + } + switch (keyword) { + case ExamplesTable: + case ExamplesTableHeaderSeparator: + case ExamplesTableIgnorableSeparator: + case ExamplesTableRow: + case ExamplesTableValueSeparator: + return true; + default: + return false; + } + } + +} diff --git a/src/com/github/kumaraman21/intellijbehave/utility/LocalizedStorySupport.java b/src/com/github/kumaraman21/intellijbehave/utility/LocalizedStorySupport.java new file mode 100644 index 0000000..b2fb1bd --- /dev/null +++ b/src/com/github/kumaraman21/intellijbehave/utility/LocalizedStorySupport.java @@ -0,0 +1,53 @@ +package com.github.kumaraman21.intellijbehave.utility; + +import org.apache.commons.lang.LocaleUtils; +import org.jbehave.core.i18n.LocalizedKeywords; +import org.jetbrains.annotations.NotNull; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author @aloyer + */ +public class LocalizedStorySupport { + + private static final Pattern LANGUAGE_PATTERN = Pattern.compile("language:[ ]*([a-z]{2}(_[A-Z]{2}(_[^ ]+)?)?)"); + + /** + * Returns the language directive if it exists. + * + * The methods checks if the {@link CharSequence} contains a language directive, + * if so it returns the corresponding locale as a {@link String} otherwise returns + * null. + * + * @param charSeq the sequence where the language directive is searched + * @return the defined locale or null + */ + public static String checkForLanguageDefinition(CharSequence charSeq) { + Matcher matcher = LANGUAGE_PATTERN.matcher(charSeq); + if (matcher.find()) { + return matcher.group(1); + } + return null; + } + + /** + * Returns the {@link LocalizedKeywords} that corresponds to the given locale. If + * the locale provided in invalid, {@link Locale#ENGLISH} is used. + * + * @param localeAsString the locale for which the {@link LocalizedKeywords} must + * be returned + */ + @NotNull + public LocalizedKeywords getKeywords(String localeAsString) { + Locale locale; + try { + locale = LocaleUtils.toLocale(localeAsString); + } + catch (Exception e) { + locale = Locale.ENGLISH; + } + return new LocalizedKeywords(locale); + } +} diff --git a/src/com/github/kumaraman21/intellijbehave/utility/ParametrizedString.java b/src/com/github/kumaraman21/intellijbehave/utility/ParametrizedString.java new file mode 100644 index 0000000..715b621 --- /dev/null +++ b/src/com/github/kumaraman21/intellijbehave/utility/ParametrizedString.java @@ -0,0 +1,416 @@ +package com.github.kumaraman21.intellijbehave.utility; + + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author @aloyer + */ +public class ParametrizedString { + + private static Pattern compileParameterPattern(String parameterPrefix) { + return Pattern.compile("(\\" + parameterPrefix + "\\w*)(\\W|\\Z)", Pattern.DOTALL); + } + + private List tokens = new ArrayList(); + private final String content; + private final String parameterPrefix; + private int parameterCount = -1; // lazily calculated + + + public ParametrizedString(String content) { + this(content, "$"); + } + + public ParametrizedString(String content, String parameterPrefix) { + if (content == null) { + throw new IllegalArgumentException("Content cannot be null"); + } + this.content = content; + this.parameterPrefix = parameterPrefix; + parse(compileParameterPattern(parameterPrefix)); + } + + @Override + public int hashCode() { + return content.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof ParametrizedString) && isSameAs((ParametrizedString) obj); + } + + public boolean isSameAs(ParametrizedString other) { + return other.content.equals(content); + } + + public String getContent() { + return content; + } + + private void parse(Pattern parameterPattern) { + Matcher matcher = parameterPattern.matcher(content); + + int prev = 0; + while (matcher.find()) { + int start = matcher.start(); + int end = matcher.end(); + if (start > prev) { + add(new Token(prev, start - prev, false)); + } + end -= matcher.group(2).length(); + start += parameterPrefix.length(); // remove prefix from the identifier + add(new Token(start, end - start, true)); + prev = end; + } + if (prev < content.length()) { + add(new Token(prev, content.length() - prev, false)); + } + } + + private void add(Token token) { + tokens.add(token); + } + + public class Token { + private final int offset; + private final int length; + private final boolean isIdentifier; + + public Token(int offset, int length, boolean isIdentifier) { + this.offset = offset; + this.length = length; + this.isIdentifier = isIdentifier; + } + + public String value() { + return content.substring(getOffset(), getOffset() + getLength()); + } + + @Override + public String toString() { + return "<<" + (isIdentifier() ? "$" : "") + value() + ">>"; + } + + public boolean regionMatches(int toffset, String other, int ooffset, int len) { + return content.regionMatches(getOffset() + toffset, other, ooffset, len); + } + + public int getOffset() { + return offset; + } + + public int getLength() { + return length; + } + + public boolean isIdentifier() { + return isIdentifier; + } + } + + public Token getToken(int index) { + return tokens.get(index); + } + + public List getTokens() { + return new ArrayList(tokens); + } + + public List getParameters() { + List parameters = new ArrayList(); + for (Token token : tokens) { + if (token.isIdentifier()) { + parameters.add(token.value()); + } + } + return parameters; + } + + public int getTokenCount() { + return tokens.size(); + } + + public int getParameterCount() { + if (parameterCount == -1) { + parameterCount = parameterCount(); + } + return parameterCount; + } + + private int parameterCount() { + int count = 0; + for(Token token : tokens) { + if(token.isIdentifier()) + count++; + } + return count; + } + + public float weightOf(String input) { + return ((float) acceptsBeginning(input)) / ((float) getTokenCount()); + } + + public int acceptsBeginning(String input) { + WeightChain chain = calculateWeightChain(input); + return chain.getWeight(); + } + + public WeightChain calculateWeightChain(String input) { + WeightChain chain = acceptsBeginning(0, input, 0); + chain.input = input; + chain.collectWeights(); + return chain; + } + + public static class StringToken { + private final String value; + private final boolean identifier; + + public StringToken(String value, boolean identifier) { + this.value = value; + this.identifier = identifier; + } + + public String getValue() { + return value; + } + + public boolean isIdentifier() { + return identifier; + } + } + + public List tokenize(String stepInput) { + + List stringTokens = new ArrayList(); + + WeightChain chain = calculateWeightChain(stepInput); + List inputTokens = chain.tokenize(); + while (chain != null) { + if (!chain.isZero()) { + Token token = tokens.get(chain.getTokenIndex()); + String value = inputTokens.get(chain.getTokenIndex()); + stringTokens.add(new StringToken(value, token.isIdentifier())); + } + chain = chain.getNext(); + } + return stringTokens; + } + + public Map extractParameterValues(String input) { + Map namedParameters = new HashMap(); + + WeightChain chain = calculateWeightChain(input); + List inputTokens = chain.tokenize(); + while (chain != null) { + if (!chain.isZero()) { + Token token = tokens.get(chain.getTokenIndex()); + if (token.isIdentifier()) { + String value = inputTokens.get(chain.getTokenIndex()); + namedParameters.put(token.value(), value); + } + } + + chain = chain.getNext(); + } + return namedParameters; + } + + private WeightChain acceptsBeginning(int inputIndex, String input, int tokenIndexStart) { + WeightChain pair = new WeightChain(); + pair.inputIndex = inputIndex; + + WeightChain current = pair; + + List tokenList = this.tokens; + for (int tokenIndex = tokenIndexStart, n = tokenList.size(); tokenIndex < n; tokenIndex++) { + boolean isLastToken = (tokenIndex == n - 1); + Token token = tokenList.get(tokenIndex); + if (!token.isIdentifier()) { + int remaining = input.length() - inputIndex; + if (remaining > token.getLength() && isLastToken) { + // more data than the token itself + return WeightChain.zero(); + } + + int overlaping = Math.min(token.getLength(), remaining); + if (overlaping > 0) { + if (token.regionMatches(0, input, inputIndex, overlaping)) { + current.tokenIndex = tokenIndex; + current.weight++; + if (overlaping == token.getLength()) // full token match + { + current.weight++; + if ((inputIndex + overlaping) == input.length()) + // no more data, break the loop now + { + return pair; + } + } // break looop + else { + return pair; + } + + inputIndex += overlaping; + WeightChain next = new WeightChain(); + next.inputIndex = inputIndex; + current.next = next; + current = next; + } + else { + // no match + return WeightChain.zero(); + } + } + else { + // not enough data, returns what has been collected + return pair; + } + } + else { + current.tokenIndex = tokenIndex; + current.weight++; + + // not the most efficient part, but no other solution right now + WeightChain next = WeightChain.zero(); + for (int j = inputIndex + 1; j < input.length(); j++) { + WeightChain sub = acceptsBeginning(j, input, tokenIndex + 1); + if (sub.hasMoreWeightThan(next)) { + next = sub; + } + } + current.next = next; + return pair; + } + } + return pair; + } + + public static class WeightChain { + public static WeightChain zero() { + return new WeightChain(); + } + + private String input; + private int inputIndex; + private int weight; + private int tokenIndex = -1; + private WeightChain next; + + public WeightChain last() { + WeightChain last = this; + WeightChain iter = this; + while (iter != null) { + if (!iter.isZero()) { + last = iter; + } + iter = iter.next; + } + return last; + } + + public boolean isZero() { + return weight == 0 && tokenIndex == -1; + } + + public int getInputIndex() { + return inputIndex; + } + + public WeightChain getNext() { + return next; + } + + public int getWeight() { + return weight; + } + + public int getTokenIndex() { + return tokenIndex; + } + + public boolean hasMoreWeightThan(WeightChain pair) { + if (weight > pair.weight) { + return true; + } + return false; + } + + @Override + public String toString() { + return "WeightChain [inputIndex=" + inputIndex + ", weight=" + weight + ", tokenIndex=" + tokenIndex + "]"; + } + + public void collectWeights() { + int w = weight; + WeightChain n = next; + while (n != null) { + if (!n.isZero()) { + w += n.weight; + } + n = n.next; + } + + this.weight = w; + } + + public List tokenize() { + List parts = new ArrayList(); + if (isZero()) { + return parts; + } + + int indexBeg = inputIndex; + WeightChain n = next; + while (n != null) { + if (!n.isZero()) { + parts.add(input.substring(indexBeg, n.inputIndex)); + indexBeg = n.inputIndex; + } + n = n.next; + } + parts.add(input.substring(indexBeg)); + + return parts; + } + } + + public boolean matches(String input) { + return acceptsBeginning(input) == (2 * getTokenCount() - getParameterCount()); + } + + public String complete(String input) { + WeightChain chain = calculateWeightChain(input); + WeightChain last = chain.last(); + if (last.isZero()) { + return ""; + } + int inputIndex = last.inputIndex; + int tokenIndex = last.tokenIndex; + + StringBuilder builder = new StringBuilder(); + + Token token = getToken(tokenIndex); + if (!token.isIdentifier()) { + int consumed = input.length() - inputIndex; + builder.append(getToken(tokenIndex).value().substring(consumed)); + } + tokenIndex++; + for (int i = tokenIndex; i < getTokenCount(); i++) { + token = getToken(i); + if (token.isIdentifier()) { + builder.append(parameterPrefix); + } + builder.append(token.value()); + } + return builder.toString(); + } + +} diff --git a/src/com/github/kumaraman21/intellijbehave/utility/StepTypeMappings.java b/src/com/github/kumaraman21/intellijbehave/utility/StepTypeMappings.java index c3398a8..c57b237 100644 --- a/src/com/github/kumaraman21/intellijbehave/utility/StepTypeMappings.java +++ b/src/com/github/kumaraman21/intellijbehave/utility/StepTypeMappings.java @@ -40,17 +40,4 @@ public class StepTypeMappings { ANNOTATION_TO_STEP_TYPE_MAPPING.put(Then.class.getName(), StepType.THEN); } - public static final Map STORY_ELEMENT_TYPE_TO_STEP_TYPE_MAPPING = new HashMap(); - static { - STORY_ELEMENT_TYPE_TO_STEP_TYPE_MAPPING.put(StoryElementType.GIVEN_STEP, StepType.GIVEN); - STORY_ELEMENT_TYPE_TO_STEP_TYPE_MAPPING.put(StoryElementType.WHEN_STEP, StepType.WHEN); - STORY_ELEMENT_TYPE_TO_STEP_TYPE_MAPPING.put(StoryElementType.THEN_STEP, StepType.THEN); - } - - public static final Map STEP_TEXT_TO_STORY_ELEMENT_TYPE_MAPPING = new HashMap(); - static { - STEP_TEXT_TO_STORY_ELEMENT_TYPE_MAPPING.put(StepType.GIVEN.name(), StoryElementType.GIVEN_STEP ); - STEP_TEXT_TO_STORY_ELEMENT_TYPE_MAPPING.put(StepType.WHEN.name(), StoryElementType.WHEN_STEP); - STEP_TEXT_TO_STORY_ELEMENT_TYPE_MAPPING.put(StepType.THEN.name(), StoryElementType.THEN_STEP); - } } diff --git a/test/com/github/kumaraman21/intellijbehave/Samples.java b/test/com/github/kumaraman21/intellijbehave/Samples.java new file mode 100644 index 0000000..3ef8fd4 --- /dev/null +++ b/test/com/github/kumaraman21/intellijbehave/Samples.java @@ -0,0 +1,127 @@ +package com.github.kumaraman21.intellijbehave; + +/** + * @author @aloyer + */ +public class Samples { + + public static final String SIMPLE_SAMPLE = + "Scenario: An unknown user cannot be logged\n" + // + "\n" + // + "Meta:\n" + // + "@skip\n" + // + "\n" + // + "Given i am the user with nickname: \"weird\"\n" + // + "When i try to login using the password \"soweird\"\n" + // + "Then i get an error message of type \"Wrong Credentials\"\n"; + + public static final String SIMPLE_FR = + "!-- language:fr\n" + + "Scénario: une simple sortie\n" + + "Etant donné que nous allons promener notre chienne\n" + + "!-- un commentaire qui n'a rien à voir\n" + + "Quand on sera dehors\n" + + "Alors elle pourra se soulager!\n" + + "Et elle sera super contente\n"; + + public static final String LONG_SAMPLE = + "Narrative: \n" + // + "In order to play a game\n" + // + "As a player\n" + // + "I want to be able to create and manage my account\n" + // + "\n" + // + "Scenario: An unknown user cannot be logged\n" + // + "\n" + // + "Meta:\n" + // + "@skip\n" + // + "\n" + // + "Given i am the user with nickname: \"weird\"\n" + // + "When i try to login using the password \"soweird\"\n" + // + "Then i get an error message of type \"Wrong Credentials\"\n" + // + "\n" + // + "\n" + // + "Scenario: A known user cannot be logged using a wrong password\n" + // + "\n" + // + "Given the following existing users:\n" + // + "| nickname | password |\n" + // + "| Travis | PacMan |\n" + // + "Given i am the user with nickname: \"Travis\"\n" + // + "When i try to login using the password \"McCallum\"\n" + // + "Then i get an error message of type \"Wrong Credentials\"\n" + // + "\n" + // + "\n" + // + "Scenario: A known user can be logged using the right password\n" + // + "\n" + // + "Given the following existing users:\n" + // + "| nickname | password |\n" + // + "| Travis | PacMan |\n" + // + "Given i am the user with nickname: \"Travis\"\n" + // + "When i try to login using the password \"PacMan\"\n" + // + "Then i get logged\n" + // + "And a welcome message is displayed\n" + // + "\n"; + + public static final String META_SAMPLE = + "Scenario: An unknown user cannot be logged\n" + // + "\n" + // + "Meta:\n" + // + "@author carmen\n" + // + "@skip\n" + // + "\n" + // + "Given i am the user with nickname: \"weird\"\n"; + + public static final String EXAMPLES_SAMPLE = + "Scenario: An unknown user cannot be logged\n" + // + "\n" + // + "Given i am the user with nickname: \"\"\n" + // + "When i try to login using the password \"soweird\"\n" + // + "Then i get an error message of type \"Wrong Credentials\"\n" + // + "\n" +// + "Examples: \n" + // + "| login | password |\n" + // + "| Travis | Pacman |\n" + // + "| Vlad | Thundercat |\n" + // + "\n" +// + "Scenario: A known user can be logged using the right password\n" + // + "\n" + // + "Given the following existing users:\n" + // + "| nickname | password |\n" + // + "| Travis | PacMan |\n" + // + "Given i am the user with nickname: \"Travis\"\n" // + ; + + public static final String COMPLEX_SAMPLE = + "Narrative: \n" + // + "In order to play a game\n" + // + "As a player\n" + // + "I want to be able to create and manage my account\n" + // + "\n" + // + "Scenario: An unknown user cannot be logged\n" + // + "\n" + // + "Meta:\n" + // + "@author turtle\n" + // + "!-- This scenario should be skipped until fixed\n" + // + "@skip\n" + // + "\n" + // + "Given i am the user with nickname: \"\"\n" + // + "!-- some weird and hard coded password\n" + // + "When i try to login using the password \"soweird\"\n" + // + "Then i get an error message of type \"Wrong Credentials\"\n" + // + "\n" +// + "Examples: \n" + // + "| login | password |\n" + // + "!-------------------------|\n" + // + "| Travis | Pacman |\n" + // + "| Vlad | Thundercat |\n" + // + "\n" + // + "\n" + // + "Scenario: A known user cannot be logged using a wrong password\n" + // + "\n" + // + "Given the following existing users:\n" + // + "| nickname | password |\n" + // + "| Travis | PacMan |\n" + // + "Given i am the user with nickname: \"Travis\"\n" + // + "When i try to login using the password \"McCallum\"\n" + // + "Then i get an error message of type \"Wrong Credentials\"\n" + // + "\n"; +} diff --git a/test/com/github/kumaraman21/intellijbehave/highlighter/StoryLexerTest.java b/test/com/github/kumaraman21/intellijbehave/highlighter/StoryLexerTest.java new file mode 100644 index 0000000..33f9cb4 --- /dev/null +++ b/test/com/github/kumaraman21/intellijbehave/highlighter/StoryLexerTest.java @@ -0,0 +1,245 @@ +package com.github.kumaraman21.intellijbehave.highlighter; + +import static com.github.kumaraman21.intellijbehave.Samples.COMPLEX_SAMPLE; +import static com.github.kumaraman21.intellijbehave.Samples.LONG_SAMPLE; +import static com.github.kumaraman21.intellijbehave.Samples.EXAMPLES_SAMPLE; +import static com.github.kumaraman21.intellijbehave.Samples.META_SAMPLE; +import static com.github.kumaraman21.intellijbehave.Samples.SIMPLE_SAMPLE; +import static org.fest.assertions.api.Assertions.assertThat; + +import com.intellij.psi.tree.IElementType; + +import org.junit.Ignore; +import org.junit.Test; + +/** + * @author @aloyer + */ +public class StoryLexerTest { + + private StoryLexer storyLexer; + + @Test + @Ignore + public void traceAll () { + //traceAll(SIMPLE_SAMPLE); + //traceAll(LONG_SAMPLE); + //traceAll(META_SAMPLE); + //traceAll(EXAMPLES_SAMPLE); + traceAll(COMPLEX_SAMPLE); + } + + @Test + public void parseSimpleSample () { + storyLexer = new StoryLexer(); + storyLexer.start(SIMPLE_SAMPLE); + + assertToken(StoryTokenType.SCENARIO_TYPE, "Scenario: "); + advanceAndAssert(StoryTokenType.SCENARIO_TEXT, "An unknown user cannot be logged"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.META, "Meta:"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.META_KEY, "@skip"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE, "Given "); + advanceAndAssert(StoryTokenType.STEP_TEXT, "i am the user with nickname: \"weird\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE, "When "); + advanceAndAssert(StoryTokenType.STEP_TEXT, "i try to login using the password \"soweird\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE, "Then "); + advanceAndAssert(StoryTokenType.STEP_TEXT, "i get an error message of type \"Wrong Credentials\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + } + + @Test + public void parseMetaSample () { + storyLexer = new StoryLexer(); + storyLexer.start(META_SAMPLE); + + assertToken(StoryTokenType.SCENARIO_TYPE, "Scenario: "); + advanceAndAssert(StoryTokenType.SCENARIO_TEXT, "An unknown user cannot be logged"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.META, "Meta:"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.META_KEY, "@author"); + advanceAndAssert(StoryTokenType.META_TEXT, " carmen"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.META_KEY, "@skip"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE, "Given "); + advanceAndAssert(StoryTokenType.STEP_TEXT, "i am the user with nickname: \"weird\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + } + + @Test + public void parseLongSample() { + storyLexer = new StoryLexer(); + storyLexer.start(LONG_SAMPLE); + + assertToken(StoryTokenType.STORY_DESCRIPTION, "Narrative: "); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STORY_DESCRIPTION, "In order to play a game"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STORY_DESCRIPTION, "As a player"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STORY_DESCRIPTION, "I want to be able to create and manage my account"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.SCENARIO_TYPE, "Scenario: "); + advanceAndAssert(StoryTokenType.SCENARIO_TEXT, "An unknown user cannot be logged"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.META, "Meta:"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.META_KEY, "@skip"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE, "Given "); + advanceAndAssert(StoryTokenType.STEP_TEXT, "i am the user with nickname: \"weird\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE, "When "); + advanceAndAssert(StoryTokenType.STEP_TEXT, "i try to login using the password \"soweird\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE, "Then "); + advanceAndAssert(StoryTokenType.STEP_TEXT, "i get an error message of type \"Wrong Credentials\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.SCENARIO_TYPE, "Scenario: "); + advanceAndAssert(StoryTokenType.SCENARIO_TEXT, "A known user cannot be logged using a wrong password"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE, "Given "); + advanceAndAssert(StoryTokenType.STEP_TEXT, "the following existing users:"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " nickname "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " password "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " Travis "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " PacMan "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE, "Given "); + advanceAndAssert(StoryTokenType.STEP_TEXT, "i am the user with nickname: \"Travis\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE, "When "); + advanceAndAssert(StoryTokenType.STEP_TEXT, "i try to login using the password \"McCallum\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE, "Then "); + advanceAndAssert(StoryTokenType.STEP_TEXT, "i get an error message of type \"Wrong Credentials\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + + // ... + } + + @Test + public void parseExamples() { + storyLexer = new StoryLexer(); + storyLexer.start(EXAMPLES_SAMPLE); + + assertToken(StoryTokenType.SCENARIO_TYPE, "Scenario: "); + advanceAndAssert(StoryTokenType.SCENARIO_TEXT, "An unknown user cannot be logged"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE, "Given "); + advanceAndAssert(StoryTokenType.STEP_TEXT, "i am the user with nickname: \"\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE, "When "); + advanceAndAssert(StoryTokenType.STEP_TEXT, "i try to login using the password \"soweird\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE, "Then "); + advanceAndAssert(StoryTokenType.STEP_TEXT, "i get an error message of type \"Wrong Credentials\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.EXAMPLE_TYPE, "Examples:"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " login "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " password "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " Travis "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " Pacman "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " Vlad "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " Thundercat "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.SCENARIO_TYPE, "Scenario: "); + advanceAndAssert(StoryTokenType.SCENARIO_TEXT, "A known user can be logged using the right password"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE, "Given "); + advanceAndAssert(StoryTokenType.STEP_TEXT, "the following existing users:"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + + // ... + + + } + + private void advanceAndAssert(IElementType storyTokenType) { + storyLexer.advance(); + assertThat(storyLexer.getTokenType()).isEqualTo(storyTokenType); + } + + private void advanceAndAssert(IElementType storyTokenType, String content) { + storyLexer.advance(); + assertToken(storyTokenType, content); + } + + private void assertToken(IElementType storyTokenType, String content) { + assertThat(storyLexer.getTokenSequence()).isEqualTo(content); + assertThat(storyLexer.getTokenType()).isEqualTo(storyTokenType); + } + + private void traceAll(String content) { + StoryLexer storyLexer = new StoryLexer(); + storyLexer.start(content); + + IElementType tokenType; + do { + tokenType = storyLexer.getTokenType(); + System.out.println("[" + + rightPad(tokenType, "STORY_DESCRIPTION".length()) + "]" + + rightPad(storyLexer.lexerState(), "IN_DIRECTIVE".length()) + + ": >>" + escape(storyLexer.getTokenSequence()) + "<<"); + + storyLexer.advance(); + tokenType = storyLexer.getTokenType(); + } + while(tokenType!=null); + } + + private String rightPad(Object object, int length) { + StringBuilder builder = new StringBuilder(object.toString()); + while(builder.length()@aloyer + */ +public class StoryLocalizedLexer_FrenchTest { + + private StoryLocalizedLexer storyLexer; + + @Test + public void parse_basicScenario() { + storyLexer = new StoryLocalizedLexer(new LocalizedStorySupport()); + storyLexer.changeLocale("fr"); + storyLexer.start("Scénario: une simple sortie\n" + + "Etant donné que nous allons promener notre chienne\n" + + "Quand on sera dehors\n" + + "Alors elle pourra se soulager!"); + + assertToken(StoryTokenType.SCENARIO_TYPE, "Scénario:"); + advanceAndAssert(StoryTokenType.SCENARIO_TEXT, " une simple sortie"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_GIVEN, "Etant donné que"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " nous allons promener notre chienne"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_WHEN, "Quand"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " on sera dehors"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_THEN, "Alors"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " elle pourra se soulager!"); + advanceAndAssert(null); + } + + @Test + public void parse_commentAllowsToSwitchLanguage() { + storyLexer = new StoryLocalizedLexer(new LocalizedStorySupport()); + // make sure one is not in fr by default + storyLexer.changeLocale("en"); + storyLexer.start("!-- language:fr\n" + + "Scénario: une simple sortie\n" + + "Etant donné que nous allons promener notre chienne\n" + + "Quand on sera dehors\n" + + "Alors elle pourra se soulager!"); + + assertToken(StoryTokenType.COMMENT_WITH_LOCALE, "!-- language:fr"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.SCENARIO_TYPE, "Scénario:"); + advanceAndAssert(StoryTokenType.SCENARIO_TEXT, " une simple sortie"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_GIVEN, "Etant donné que"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " nous allons promener notre chienne"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_WHEN, "Quand"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " on sera dehors"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_THEN, "Alors"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " elle pourra se soulager!"); + advanceAndAssert(null); + } + + private void advanceAndAssert(@Nullable IElementType storyTokenType) { + storyLexer.advance(); + assertThat(storyLexer.getTokenType()).isEqualTo(storyTokenType); + } + + private void advanceAndAssert(IElementType storyTokenType, String content) { + storyLexer.advance(); + assertToken(storyTokenType, content); + } + + private void assertToken(IElementType storyTokenType, String content) { + assertThat(storyLexer.getTokenSequence()).isEqualTo(content); + assertThat(storyLexer.getTokenType()).isEqualTo(storyTokenType); + } +} diff --git a/test/com/github/kumaraman21/intellijbehave/highlighter/StoryLocalizedLexer_MalformedTest.java b/test/com/github/kumaraman21/intellijbehave/highlighter/StoryLocalizedLexer_MalformedTest.java new file mode 100644 index 0000000..24dc85f --- /dev/null +++ b/test/com/github/kumaraman21/intellijbehave/highlighter/StoryLocalizedLexer_MalformedTest.java @@ -0,0 +1,119 @@ +package com.github.kumaraman21.intellijbehave.highlighter; + +import static org.fest.assertions.api.Assertions.assertThat; + +import com.github.kumaraman21.intellijbehave.utility.LocalizedStorySupport; +import com.intellij.psi.tree.IElementType; + +import org.jetbrains.annotations.Nullable; +import org.junit.Test; + +/** + * @author @aloyer + */ +public class StoryLocalizedLexer_MalformedTest { + + private StoryLocalizedLexer storyLexer; + + @Test + public void parse_tableWithoutPreamble() { + storyLexer = new StoryLocalizedLexer(new LocalizedStorySupport()); + storyLexer.start("| Travis | Pacman |"); + + assertToken(StoryTokenType.TABLE_DELIM, "|"); + advanceAndAssert(StoryTokenType.TABLE_CELL, " Travis "); + advanceAndAssert(StoryTokenType.TABLE_DELIM, "|"); + advanceAndAssert(StoryTokenType.TABLE_CELL, " Pacman "); + advanceAndAssert(StoryTokenType.TABLE_DELIM, "|"); + advanceAndAssert(null); + } + + @Test + public void parse_scenarioWithoutText() { + storyLexer = new StoryLocalizedLexer(new LocalizedStorySupport()); + storyLexer.start("Scenario:\n" + + "Given\n" + + "When\n" + + "Then\n" + + "And\n"); + + assertToken(StoryTokenType.SCENARIO_TYPE, "Scenario:"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_GIVEN, "Given"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_WHEN, "When"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_THEN, "Then"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_AND, "And"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(null); + } + + @Test + public void parse_scenarioWithStepAsText() { + storyLexer = new StoryLocalizedLexer(new LocalizedStorySupport()); + storyLexer.start("Scenario: Given a nice\n" + + "Given\n" + + "When\n" + + "Then\n"); + + assertToken(StoryTokenType.SCENARIO_TYPE, "Scenario:"); + advanceAndAssert(StoryTokenType.SCENARIO_TEXT, " Given a nice"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_GIVEN, "Given"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_WHEN, "When"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_THEN, "Then"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(null); + } + + private void advanceAndAssert(@Nullable IElementType storyTokenType) { + storyLexer.advance(); + assertThat(storyLexer.getTokenType()).isEqualTo(storyTokenType); + } + + private void advanceAndAssert(IElementType storyTokenType, String content) { + storyLexer.advance(); + assertToken(storyTokenType, content); + } + + private void assertToken(IElementType storyTokenType, String content) { + assertThat(storyLexer.getTokenSequence()).isEqualTo(content); + assertThat(storyLexer.getTokenType()).isEqualTo(storyTokenType); + } + + private static void traceAll(String content, StoryLocalizedLexer storyLexer) { + storyLexer.start(content); + + IElementType tokenType; + do { + tokenType = storyLexer.getTokenType(); + System.out.println( + rightPad("" + storyLexer.getPosition(), 3) + " " + + "[" + rightPad(tokenType, "STORY_DESCRIPTION".length()) + "]" + + rightPad(storyLexer.lexerState(), "IN_DIRECTIVE".length()) + + ": >>" + escape(storyLexer.getTokenSequence()) + "<<"); + + storyLexer.advance(); + tokenType = storyLexer.getTokenType(); + } + while (tokenType != null); + } + + private static String rightPad(Object object, int length) { + StringBuilder builder = new StringBuilder(object.toString()); + while (builder.length() < length) { + builder.append(" "); + } + return builder.toString(); + } + + private static String escape(CharSequence tokenSequence) { + return tokenSequence.toString().replace("\n", "\\n").replace("\r", "\\r"); + } + + +} diff --git a/test/com/github/kumaraman21/intellijbehave/highlighter/StoryLocalizedLexer_SamplesTest.java b/test/com/github/kumaraman21/intellijbehave/highlighter/StoryLocalizedLexer_SamplesTest.java new file mode 100644 index 0000000..33a299a --- /dev/null +++ b/test/com/github/kumaraman21/intellijbehave/highlighter/StoryLocalizedLexer_SamplesTest.java @@ -0,0 +1,235 @@ +package com.github.kumaraman21.intellijbehave.highlighter; + +import static com.github.kumaraman21.intellijbehave.Samples.EXAMPLES_SAMPLE; +import static com.github.kumaraman21.intellijbehave.Samples.LONG_SAMPLE; +import static com.github.kumaraman21.intellijbehave.Samples.META_SAMPLE; +import static com.github.kumaraman21.intellijbehave.Samples.SIMPLE_SAMPLE; +import static org.fest.assertions.api.Assertions.assertThat; + +import com.github.kumaraman21.intellijbehave.utility.LocalizedStorySupport; +import com.intellij.psi.tree.IElementType; + +import org.junit.Ignore; +import org.junit.Test; + +/** + * @author @aloyer + */ +public class StoryLocalizedLexer_SamplesTest { + + private StoryLocalizedLexer storyLexer; + + @Test + @Ignore + public void traceAll() { + //traceAll(SIMPLE_SAMPLE); + traceAll(LONG_SAMPLE); + //traceAll(META_SAMPLE); + //traceAll(EXAMPLES_SAMPLE); + //traceAll(COMPLEX_SAMPLE); + } + + @Test + public void parseSimpleSample() { + storyLexer = new StoryLocalizedLexer(new LocalizedStorySupport()); + storyLexer.start(SIMPLE_SAMPLE); + + assertToken(StoryTokenType.SCENARIO_TYPE, "Scenario:"); + advanceAndAssert(StoryTokenType.SCENARIO_TEXT, " An unknown user cannot be logged"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.META, "Meta:"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.META_KEY, "@skip"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_GIVEN, "Given"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " i am the user with nickname: \"weird\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_WHEN, "When"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " i try to login using the password \"soweird\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_THEN, "Then"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " i get an error message of type \"Wrong Credentials\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + } + + @Test + public void parseMetaSample() { + storyLexer = new StoryLocalizedLexer(new LocalizedStorySupport()); + storyLexer.start(META_SAMPLE); + + assertToken(StoryTokenType.SCENARIO_TYPE, "Scenario:"); + advanceAndAssert(StoryTokenType.SCENARIO_TEXT, " An unknown user cannot be logged"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.META, "Meta:"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.META_KEY, "@author"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.META_TEXT, "carmen"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.META_KEY, "@skip"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_GIVEN, "Given"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " i am the user with nickname: \"weird\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + } + + @Test + public void parseLongSample() { + storyLexer = new StoryLocalizedLexer(new LocalizedStorySupport()); + storyLexer.start(LONG_SAMPLE); + + assertToken(StoryTokenType.NARRATIVE_TYPE, "Narrative:"); + advanceAndAssert(StoryTokenType.STORY_DESCRIPTION, " "); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.NARRATIVE_TYPE, "In order to"); + advanceAndAssert(StoryTokenType.STORY_DESCRIPTION, " play a game"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.NARRATIVE_TYPE, "As a"); + advanceAndAssert(StoryTokenType.STORY_DESCRIPTION, " player"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.NARRATIVE_TYPE, "I want to"); + advanceAndAssert(StoryTokenType.STORY_DESCRIPTION, " be able to create and manage my account"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.SCENARIO_TYPE, "Scenario:"); + advanceAndAssert(StoryTokenType.SCENARIO_TEXT, " An unknown user cannot be logged"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.META, "Meta:"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.META_KEY, "@skip"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_GIVEN, "Given"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " i am the user with nickname: \"weird\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_WHEN, "When"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " i try to login using the password \"soweird\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_THEN, "Then"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " i get an error message of type \"Wrong Credentials\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.SCENARIO_TYPE, "Scenario:"); + advanceAndAssert(StoryTokenType.SCENARIO_TEXT, " A known user cannot be logged using a wrong password"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_GIVEN, "Given"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " the following existing users:"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " nickname "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " password "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " Travis "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " PacMan "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_GIVEN, "Given"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " i am the user with nickname: \"Travis\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_WHEN, "When"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " i try to login using the password \"McCallum\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_THEN, "Then"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " i get an error message of type \"Wrong Credentials\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + + // ... + } + + @Test + public void parseExamples() { + storyLexer = new StoryLocalizedLexer(new LocalizedStorySupport()); + storyLexer.start(EXAMPLES_SAMPLE); + + assertToken(StoryTokenType.SCENARIO_TYPE, "Scenario:"); + advanceAndAssert(StoryTokenType.SCENARIO_TEXT, " An unknown user cannot be logged"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_GIVEN, "Given"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " i am the user with nickname: \"\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_WHEN, "When"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " i try to login using the password \"soweird\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_THEN, "Then"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " i get an error message of type \"Wrong Credentials\""); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.EXAMPLE_TYPE, "Examples:"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " login "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " password "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " Travis "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " Pacman "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " Vlad "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.TABLE_CELL, " Thundercat "); + advanceAndAssert(StoryTokenType.TABLE_DELIM); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.SCENARIO_TYPE, "Scenario:"); + advanceAndAssert(StoryTokenType.SCENARIO_TEXT, " A known user can be logged using the right password"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + advanceAndAssert(StoryTokenType.STEP_TYPE_GIVEN, "Given"); + advanceAndAssert(StoryTokenType.STEP_TEXT, " the following existing users:"); + advanceAndAssert(StoryTokenType.WHITE_SPACE); + + // ... + } + + private void advanceAndAssert(IElementType storyTokenType) { + storyLexer.advance(); + assertThat(storyLexer.getTokenType()).isEqualTo(storyTokenType); + } + + private void advanceAndAssert(IElementType storyTokenType, String content) { + storyLexer.advance(); + assertToken(storyTokenType, content); + } + + private void assertToken(IElementType storyTokenType, String content) { + assertThat(storyLexer.getTokenSequence()).isEqualTo(content); + assertThat(storyLexer.getTokenType()).isEqualTo(storyTokenType); + } + + private void traceAll(String content) { + storyLexer = new StoryLocalizedLexer(new LocalizedStorySupport()); + storyLexer.start(content); + + IElementType tokenType; + do { + tokenType = storyLexer.getTokenType(); + System.out.println( + rightPad("" + storyLexer.getPosition(), 3) + " " + + "[" + rightPad(tokenType, "STORY_DESCRIPTION".length()) + "]" + + rightPad(storyLexer.lexerState(), "IN_DIRECTIVE".length()) + + ": >>" + escape(storyLexer.getTokenSequence()) + "<<"); + + storyLexer.advance(); + tokenType = storyLexer.getTokenType(); + } + while (tokenType != null); + } + + private String rightPad(Object object, int length) { + StringBuilder builder = new StringBuilder(object.toString()); + while (builder.length() < length) { + builder.append(" "); + } + return builder.toString(); + } + + private String escape(CharSequence tokenSequence) { + return tokenSequence.toString().replace("\n", "\\n").replace("\r", "\\r"); + } + + +} diff --git a/test/com/github/kumaraman21/intellijbehave/parser/IntelliJBehaveBaseTestCase.java b/test/com/github/kumaraman21/intellijbehave/parser/IntelliJBehaveBaseTestCase.java new file mode 100644 index 0000000..8628455 --- /dev/null +++ b/test/com/github/kumaraman21/intellijbehave/parser/IntelliJBehaveBaseTestCase.java @@ -0,0 +1,112 @@ +package com.github.kumaraman21.intellijbehave.parser; + +import com.github.kumaraman21.intellijbehave.Samples; +import com.github.kumaraman21.intellijbehave.highlighter.StoryLexerFactory; +import com.intellij.lang.ASTNode; +import com.intellij.lang.PsiBuilder; +import com.intellij.lang.impl.PsiBuilderImpl; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.impl.DebugUtil; +import com.intellij.testFramework.PsiTestCase; +import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; +import com.intellij.testFramework.fixtures.TestFixtureBuilder; + +/** + * @author @aloyer + */ +public class IntelliJBehaveBaseTestCase extends PsiTestCase { + + private Project myProject; + protected IdeaProjectTestFixture myFixture; + protected CodeStyleSettings mySettings; + + public Project getProject() { + return myProject; + } + + protected CodeStyleSettings getSettings() { + return CodeStyleSettingsManager.getSettings(myProject); + } + +// protected void setSettings() { +// final StoryFileType fileType = StoryFileType.STORY_FILE_TYPE; +// mySettings = getSettings(); +// mySettings.getIndentOptions(fileType).INDENT_SIZE = 2; +// mySettings.getIndentOptions(fileType).CONTINUATION_INDENT_SIZE = 2; +// mySettings.getIndentOptions(fileType).TAB_SIZE = 2; +// } + + protected void setUp() throws Exception { + System.setProperty("idea.platform.prefix", "Idea"); + myFixture = createFixture(); + myFixture.setUp(); + myProject = myFixture.getProject(); + } + + protected IdeaProjectTestFixture createFixture() { + TestFixtureBuilder fixtureBuilder = IdeaTestFixtureFactory.getFixtureFactory().createLightFixtureBuilder(); + return fixtureBuilder.getFixture(); + } + + protected void tearDown() { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + try { + if(!myFixture.getProject().isDisposed()) + myFixture.tearDown(); + } catch (Exception e) { + // mute + } + } + }); + } + + public void testCase_Simple () { + ASTNode astNode = doParse(Samples.SIMPLE_SAMPLE); + System.out.println("IntelliJBehaveBaseTestCase.testCase_Simple: " + DebugUtil.treeToString(astNode, false)); + } + + public void testCase_Long () { + ASTNode astNode = doParse(Samples.LONG_SAMPLE); + System.out.println("IntelliJBehaveBaseTestCase.testCase_Long: " + DebugUtil.treeToString(astNode, false)); + } + + public void testCase_Meta () { + ASTNode astNode = doParse(Samples.META_SAMPLE); + System.out.println("IntelliJBehaveBaseTestCase.testCase_Meta: " + DebugUtil.treeToString(astNode, false)); + } + + public void testCase_Examples () { + ASTNode astNode = doParse(Samples.EXAMPLES_SAMPLE); + System.out.println("IntelliJBehaveBaseTestCase.testCase_Examples: " + DebugUtil.treeToString(astNode, false)); + } + + public void testCase_Complex() { + ASTNode astNode = doParse(Samples.COMPLEX_SAMPLE); + System.out.println("IntelliJBehaveBaseTestCase.testCase_Complex: " + DebugUtil.treeToString(astNode, false)); + } + + public void testCase_SimpleFR() { + ASTNode astNode = doParse(Samples.SIMPLE_FR); + System.out.println("IntelliJBehaveBaseTestCase.testCase_Complex: " + DebugUtil.treeToString(astNode, false)); + } + + private ASTNode doParse(String content) { + PsiBuilder builder = new PsiBuilderImpl(myProject, + null, + new StoryParserDefinition(), + new StoryLexerFactory().createLexer(), + null, + content, + null, + null); + + StoryParser parser = new StoryParser(); + return parser.parse(StoryElementType.STORY_FILE, builder); + } + +} diff --git a/test/com/github/kumaraman21/intellijbehave/parser/StoryParserTest.java b/test/com/github/kumaraman21/intellijbehave/parser/StoryParserTest.java new file mode 100644 index 0000000..442b6ab --- /dev/null +++ b/test/com/github/kumaraman21/intellijbehave/parser/StoryParserTest.java @@ -0,0 +1,29 @@ +package com.github.kumaraman21.intellijbehave.parser; + +import com.github.kumaraman21.intellijbehave.Samples; +import com.github.kumaraman21.intellijbehave.highlighter.StoryLexer; +import com.intellij.lang.PsiBuilder; +import com.intellij.lang.impl.PsiBuilderImpl; +import com.intellij.openapi.project.Project; + +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mockito; + +/** + * @author @aloyer + * + * @see IntelliJBehaveBaseTestCase + */ +@Ignore("... need a full plateform initialization!?!") +public class StoryParserTest { + + @Test + public void test1() throws Throwable { + Project project = Mockito.mock(Project.class); + PsiBuilder builder = new PsiBuilderImpl(project, null, new StoryParserDefinition(), new StoryLexer(), null, Samples.SIMPLE_SAMPLE, null, null); + + StoryParser parser = new StoryParser(); + parser.parse(StoryElementType.STORY_FILE, builder); + } +} diff --git a/test/com/github/kumaraman21/intellijbehave/utility/CharTreeTest.java b/test/com/github/kumaraman21/intellijbehave/utility/CharTreeTest.java new file mode 100644 index 0000000..c2b00ec --- /dev/null +++ b/test/com/github/kumaraman21/intellijbehave/utility/CharTreeTest.java @@ -0,0 +1,34 @@ +package com.github.kumaraman21.intellijbehave.utility; + +import static com.github.kumaraman21.intellijbehave.utility.JBKeyword.Narrative; +import static org.fest.assertions.api.Assertions.assertThat; + +import com.github.kumaraman21.intellijbehave.utility.CharTree; +import com.github.kumaraman21.intellijbehave.utility.JBKeyword; + +import org.jbehave.core.i18n.LocalizedKeywords; +import org.junit.Before; +import org.junit.Test; + +/** + * @author @aloyer + */ +public class CharTreeTest { + + private CharTree charTree; + + @Before + public void setUp () { + LocalizedKeywords keywords = new LocalizedKeywords(); + charTree = new CharTree('/', null); + for (JBKeyword kw : JBKeyword.values()) { + String asString = kw.asString(keywords); + charTree.push(asString, kw); + } + } + + @Test + public void narrative_doubleDot() { + assertThat(charTree.lookup("Narrative: \n", 0)).isEqualTo(new CharTree.Entry(Narrative, 10)); + } +} diff --git a/test/com/github/kumaraman21/intellijbehave/utility/LocalizedStorySupportTest.java b/test/com/github/kumaraman21/intellijbehave/utility/LocalizedStorySupportTest.java new file mode 100644 index 0000000..c51dbd2 --- /dev/null +++ b/test/com/github/kumaraman21/intellijbehave/utility/LocalizedStorySupportTest.java @@ -0,0 +1,25 @@ +package com.github.kumaraman21.intellijbehave.utility; + +import static org.fest.assertions.api.Assertions.assertThat; + +import org.junit.Test; + +/** + * @author @aloyer + */ +public class LocalizedStorySupportTest { + + @Test + public void checkForLanguageDefinition_validCases() { + assertThat(LocalizedStorySupport.checkForLanguageDefinition(" !-- language:fr")).isEqualTo("fr"); + assertThat(LocalizedStorySupport.checkForLanguageDefinition(" !-- language: fr")).isEqualTo("fr"); + assertThat(LocalizedStorySupport.checkForLanguageDefinition(" !-- language: fr ")).isEqualTo("fr"); + assertThat(LocalizedStorySupport.checkForLanguageDefinition(" !-- language: fr ")).isEqualTo("fr"); + } + @Test + public void checkForLanguageDefinition_invalidCases() { + assertThat(LocalizedStorySupport.checkForLanguageDefinition(" !-- languge:fr")).isNull(); + assertThat(LocalizedStorySupport.checkForLanguageDefinition(" !-- Language: fr")).isNull(); + assertThat(LocalizedStorySupport.checkForLanguageDefinition(" !-- lang: fr ")).isNull(); + } +} diff --git a/testData/story/test1.story b/testData/story/test1.story new file mode 100644 index 0000000..e69de29 diff --git a/testLib/fest-assert-core-2.0M5.jar b/testLib/fest-assert-core-2.0M5.jar new file mode 100644 index 0000000..e7c4b78 Binary files /dev/null and b/testLib/fest-assert-core-2.0M5.jar differ diff --git a/testLib/fest-util-1.2.0.jar b/testLib/fest-util-1.2.0.jar new file mode 100644 index 0000000..560c705 Binary files /dev/null and b/testLib/fest-util-1.2.0.jar differ diff --git a/testLib/junit-4.10.jar b/testLib/junit-4.10.jar new file mode 100644 index 0000000..954851e Binary files /dev/null and b/testLib/junit-4.10.jar differ diff --git a/testLib/mockito-all-1.9.0.jar b/testLib/mockito-all-1.9.0.jar new file mode 100644 index 0000000..273fd50 Binary files /dev/null and b/testLib/mockito-all-1.9.0.jar differ