diff --git a/src/AddIns/BackendBindings/JavaScriptBinding/Project/JavaScriptBinding.csproj b/src/AddIns/BackendBindings/JavaScriptBinding/Project/JavaScriptBinding.csproj index 94b53c1c27..e94962270b 100644 --- a/src/AddIns/BackendBindings/JavaScriptBinding/Project/JavaScriptBinding.csproj +++ b/src/AddIns/BackendBindings/JavaScriptBinding/Project/JavaScriptBinding.csproj @@ -57,11 +57,15 @@ + + + + diff --git a/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/ITokenExtensions.cs b/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/ITokenExtensions.cs index 0e489f2b19..abd1acbef6 100644 --- a/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/ITokenExtensions.cs +++ b/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/ITokenExtensions.cs @@ -3,6 +3,7 @@ using System; using Antlr.Runtime; +using Xebic.Parsers.ES3; namespace ICSharpCode.JavaScriptBinding { @@ -17,5 +18,10 @@ namespace ICSharpCode.JavaScriptBinding { return token.CharPositionInLine + token.Text.Length + 1; } + + public static bool IsSingleLineComment(this IToken token) + { + return token.Type == ES3Lexer.SingleLineComment; + } } } diff --git a/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptAst.cs b/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptAst.cs index 0a61b2369c..c8fc5f39c8 100644 --- a/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptAst.cs +++ b/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptAst.cs @@ -2,6 +2,7 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; +using System.Collections.Generic; using Antlr.Runtime; using Antlr.Runtime.Tree; @@ -30,5 +31,10 @@ namespace ICSharpCode.JavaScriptBinding { return tokenStream.Get(index); } + + public IList GetTokens() + { + return tokenStream.GetTokens(); + } } } diff --git a/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptAstWalker.cs b/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptAstWalker.cs index 7fd0908d80..e05aee7df6 100644 --- a/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptAstWalker.cs +++ b/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptAstWalker.cs @@ -26,6 +26,7 @@ namespace ICSharpCode.JavaScriptBinding { if (ast.IsValid) { Walk(ast.Tree); + WalkRegions(); } } @@ -52,5 +53,11 @@ namespace ICSharpCode.JavaScriptBinding Walk(child); } } + + void WalkRegions() + { + var regionWalker = new JavaScriptRegionWalker(ast, compilationUnit); + regionWalker.Walk(); + } } } diff --git a/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptRegion.cs b/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptRegion.cs new file mode 100644 index 0000000000..adb9dddf15 --- /dev/null +++ b/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptRegion.cs @@ -0,0 +1,42 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using ICSharpCode.SharpDevelop.Dom; + +namespace ICSharpCode.JavaScriptBinding +{ + public class JavaScriptRegion + { + JavaScriptRegionStart start; + JavaScriptRegionEnd end; + + public JavaScriptRegion(JavaScriptRegionStart start, JavaScriptRegionEnd end) + { + this.start = start; + this.end = end; + } + + public void AddRegion(IList foldingRegions) + { + FoldingRegion namedFoldingRegion = CreateFoldingRegion(); + foldingRegions.Add(namedFoldingRegion); + } + + FoldingRegion CreateFoldingRegion() + { + DomRegion location = GetRegionLocation(); + return new FoldingRegion(start.Name, location); + } + + DomRegion GetRegionLocation() + { + int beginLine = start.Line; + int endLine = end.Line; + int beginColumn = start.StartColumn; + int endColumn = end.EndColumn; + return new DomRegion(beginLine, beginColumn, endLine, endColumn); + } + } +} diff --git a/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptRegionEnd.cs b/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptRegionEnd.cs new file mode 100644 index 0000000000..7059416d6e --- /dev/null +++ b/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptRegionEnd.cs @@ -0,0 +1,34 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using Antlr.Runtime; + +namespace ICSharpCode.JavaScriptBinding +{ + public class JavaScriptRegionEnd + { + public static readonly string RegionEndText = "//#endregion"; + public static readonly int RegionEndTextLength = RegionEndText.Length; + + IToken token; + + public JavaScriptRegionEnd(IToken token) + { + this.token = token; + } + + public static bool IsRegionEnd(IToken token) + { + return token.Text.StartsWith(RegionEndText); + } + + public int Line { + get { return token.Line; } + } + + public int EndColumn { + get { return token.BeginColumn() + RegionEndTextLength; } + } + } +} diff --git a/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptRegionStart.cs b/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptRegionStart.cs new file mode 100644 index 0000000000..2fce79b57b --- /dev/null +++ b/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptRegionStart.cs @@ -0,0 +1,51 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using Antlr.Runtime; + +namespace ICSharpCode.JavaScriptBinding +{ + public class JavaScriptRegionStart + { + public static readonly string RegionStartText = "//#region "; + public static readonly int RegionStartTextLength = RegionStartText.Length; + + IToken token; + string name; + + public JavaScriptRegionStart(IToken token) + { + this.token = token; + } + + public string Name { + get { + if (name == null) { + GetName(); + } + return name; + } + } + + void GetName() + { + string text = token.Text; + int index = text.IndexOf(RegionStartText); + name = text.Substring(index + RegionStartTextLength); + } + + public static bool IsRegionStart(IToken token) + { + return token.Text.StartsWith(RegionStartText); + } + + public int Line { + get { return token.Line; } + } + + public int StartColumn { + get { return token.BeginColumn(); } + } + } +} diff --git a/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptRegionWalker.cs b/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptRegionWalker.cs new file mode 100644 index 0000000000..7c17023b3a --- /dev/null +++ b/src/AddIns/BackendBindings/JavaScriptBinding/Project/Src/JavaScriptRegionWalker.cs @@ -0,0 +1,64 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using Antlr.Runtime; +using Antlr.Runtime.Tree; +using ICSharpCode.SharpDevelop.Dom; +using Xebic.Parsers.ES3; + +namespace ICSharpCode.JavaScriptBinding +{ + public class JavaScriptRegionWalker + { + JavaScriptAst ast; + ICompilationUnit compilationUnit; + Stack regions; + + public JavaScriptRegionWalker( + JavaScriptAst ast, + ICompilationUnit compilationUnit) + { + this.ast = ast; + this.compilationUnit = compilationUnit; + } + + public void Walk() + { + regions = new Stack(); + + foreach (IToken token in ast.GetTokens()) { + if (token.IsSingleLineComment()) { + WalkComment(token); + } + } + } + + void WalkComment(IToken token) + { + if (JavaScriptRegionStart.IsRegionStart(token)) { + WalkRegionStart(token); + } else if (JavaScriptRegionEnd.IsRegionEnd(token)) { + WalkRegionEnd(token); + } + } + + void WalkRegionStart(IToken token) + { + var regionStart = new JavaScriptRegionStart(token); + regions.Push(regionStart); + } + + void WalkRegionEnd(IToken token) + { + if (regions.Count > 0) { + JavaScriptRegionStart regionStart = regions.Pop(); + + var regionEnd = new JavaScriptRegionEnd(token); + var region = new JavaScriptRegion(regionStart, regionEnd); + region.AddRegion(compilationUnit.FoldingRegions); + } + } + } +} diff --git a/src/AddIns/BackendBindings/JavaScriptBinding/Test/Src/JavaScriptParserTests.cs b/src/AddIns/BackendBindings/JavaScriptBinding/Test/Src/JavaScriptParserTests.cs index df12865e73..9e8ecf95b6 100644 --- a/src/AddIns/BackendBindings/JavaScriptBinding/Test/Src/JavaScriptParserTests.cs +++ b/src/AddIns/BackendBindings/JavaScriptBinding/Test/Src/JavaScriptParserTests.cs @@ -66,6 +66,14 @@ namespace JavaScriptBinding.Tests get { return FirstClass.Methods[0]; } } + FoldingRegion FirstRegion { + get { return compilationUnit.FoldingRegions[0]; } + } + + FoldingRegion SecondRegion { + get { return compilationUnit.FoldingRegions[1]; } + } + [Test] public void CanParse_CSharpProjectPassed_ReturnsTrue() { @@ -279,5 +287,221 @@ namespace JavaScriptBinding.Tests Assert.AreEqual(expectedRegion, methodBodyRegion); } + + [Test] + public void Parse_JavaScriptCodeHasOneRegion_OneFoldingRegionAddedToCompilationUnit() + { + string code = + "//#region MyRegion\r\n" + + "var a = 1;\r\n" + + "//#endregion\r\n"; + + ParseJavaScript(code); + + int count = compilationUnit.FoldingRegions.Count; + + Assert.AreEqual(1, count); + } + + [Test] + public void Parse_JavaScriptCodeHasOneRegionWithName_FoldingRegionAddedWithName() + { + string code = + "//#region MyRegion\r\n" + + "var a = 1;\r\n" + + "//#endregion\r\n"; + + ParseJavaScript(code); + + string name = FirstRegion.Name; + + Assert.AreEqual("MyRegion", name); + } + + [Test] + public void Parse_JavaScriptCodeHasNoRegions_NoFoldingRegionsAddedToCompilationUnit() + { + string code = "var a = 1;"; + + ParseJavaScript(code); + + int count = compilationUnit.FoldingRegions.Count; + + Assert.AreEqual(0, count); + } + + [Test] + public void Parse_JavaScriptCodeSingleCommentEndsWithRegionTextButDoesNotStartWithRegion_NoRegionsAddedToCompilationUnit() + { + string code = + "//not a #region test\r\n" + + "var a = 1;\r\n" + + "//not an #endregion\r\n"; + + ParseJavaScript(code); + + Assert.AreEqual(0, compilationUnit.FoldingRegions.Count); + } + + [Test] + public void Parse_JavaScriptCodeHasOneRegion_RegionLocationIsSpecified() + { + string code = + "//#region MyRegion\r\n" + + "var a = 1;\r\n" + + "//#endregion\r\n"; + + ParseJavaScript(code); + + DomRegion location = FirstRegion.Region; + + int beginLine = 1; + int endLine = 3; + int beginColumn = 1; + int endColumn = 13; + + var expectedLocation = new DomRegion(beginLine, beginColumn, endLine, endColumn); + + Assert.AreEqual(expectedLocation, location); + } + + [Test] + public void Parse_JavaScriptCodeHasRegionStartOnly_NoRegionsAddedToCompilationUnit() + { + string code = + "//#region MyRegion\r\n" + + "var a = 1;\r\n"; + + ParseJavaScript(code); + + Assert.AreEqual(0, compilationUnit.FoldingRegions.Count); + } + + [Test] + public void Parse_JavaScriptCodeHasOneRegionWithEndRegionFollowedByText_TextFollowingEndRegionIsNotPartOfFold() + { + string code = + "//#region MyRegion\r\n" + + "var a = 1;\r\n" + + "//#endregion abc\r\n"; + + ParseJavaScript(code); + + DomRegion location = FirstRegion.Region; + + int beginLine = 1; + int endLine = 3; + int beginColumn = 1; + int endColumn = 13; + + var expectedLocation = new DomRegion(beginLine, beginColumn, endLine, endColumn); + + Assert.AreEqual(expectedLocation, location); + } + + [Test] + public void Parse_JavaScriptCodeHasTwoRegions_TwoRegionsAddedToCompilationUnit() + { + string code = + "//#region One\r\n" + + "var a = 1;\r\n" + + "//#endregion\r\n" + + "\r\n" + + "//#region Two\r\n" + + "var b = 1;\r\n" + + "//#endregion\r\n"; + + ParseJavaScript(code); + + Assert.AreEqual(2, compilationUnit.FoldingRegions.Count); + } + + [Test] + public void Parse_JavaScriptCodeHasTwoRegions_RegionsHaveCorrectLocationsAndNames() + { + string code = + "//#region One\r\n" + + "var a = 1;\r\n" + + "//#endregion\r\n" + + "\r\n" + + "//#region Two\r\n" + + "var b = 1;\r\n" + + "//#endregion\r\n"; + + ParseJavaScript(code); + + DomRegion firstRegionLocation = FirstRegion.Region; + + int beginLine = 1; + int endLine = 3; + int beginColumn = 1; + int endColumn = 13; + + var expectedFirstRegionLocation = new DomRegion(beginLine, beginColumn, endLine, endColumn); + + DomRegion secondRegionLocation = SecondRegion.Region; + + beginLine = 5; + endLine = 7; + beginColumn = 1; + endColumn = 13; + + var expectedSecondRegionLocation = new DomRegion(beginLine, beginColumn, endLine, endColumn); + + Assert.AreEqual(expectedFirstRegionLocation, firstRegionLocation); + Assert.AreEqual("One", FirstRegion.Name); + Assert.AreEqual(expectedSecondRegionLocation, secondRegionLocation); + Assert.AreEqual("Two", SecondRegion.Name); + } + + [Test] + public void Parse_JavaScriptCodeHasEndRegionButNoStartRegion_NoRegionsAddedToCompilationUnit() + { + string code = + "var a = 1;\r\n" + + "//#endregion\r\n"; + + ParseJavaScript(code); + + Assert.AreEqual(0, compilationUnit.FoldingRegions.Count); + } + + [Test] + public void Parse_JavaScriptCodeHasTwoNestedRegions_RegionsHaveCorrectLocationsAndNames() + { + string code = + "//#region One\r\n" + + "var a = 1;\r\n" + + "//#region Two\r\n" + + "var b = 1;\r\n" + + "//#endregion\r\n" + + "var c = 1;\r\n" + + "//#endregion\r\n"; + + ParseJavaScript(code); + + DomRegion firstRegionLocation = FirstRegion.Region; + + int beginLine = 3; + int endLine = 5; + int beginColumn = 1; + int endColumn = 13; + + var expectedFirstRegionLocation = new DomRegion(beginLine, beginColumn, endLine, endColumn); + + DomRegion secondRegionLocation = SecondRegion.Region; + + beginLine = 1; + endLine = 7; + beginColumn = 1; + endColumn = 13; + + var expectedSecondRegionLocation = new DomRegion(beginLine, beginColumn, endLine, endColumn); + + Assert.AreEqual(expectedFirstRegionLocation, firstRegionLocation); + Assert.AreEqual("Two", FirstRegion.Name); + Assert.AreEqual(expectedSecondRegionLocation, secondRegionLocation); + Assert.AreEqual("One", SecondRegion.Name); + } } }