TabularEditor
TabularEditor copied to clipboard
Error in TE2 2.18.1 trying to run script that works in TE3 -- even using Roslyn compiler of Visual Studio 2022
Describe the bug Trying to execute in TE2 a script that runs fine in TE3 I get an error I don't know how to fix, or if it's fixable.
To Reproduce Steps to reproduce the behavior: Execute the script `#r "System.IO" #r "Microsoft.CodeAnalysis" using System.IO; using System.Windows.Forms; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax;
// '2023-05-06 / B.Agullo /
// this macro copies the code of any of the methods defined in the TE_Scripts.cs File
// if the macro is using the custom class it must include de following commented directive
// //using GeneralFunctions;
// if this line is found the macro will copy the code also from the class defined in GeneralFunctions
// and will combine the commented references of the class with those of the macro
// once the macro finishes the code is in the clipboard so it can be pasted
// in a new c# script tab in Tabular Editor, using CTRL+V
// see further detail at --
//config
String macroFilePath = @"C:\Crea una carpeta\TE-Scripting-in-Visual-Studio\TE Scripts\TE Scripts.cs";
String customClassFilePath = @"C:\Crea una carpeta\TE-Scripting-in-Visual-Studio\GeneralFunctions\GeneralFunctions.cs";
String codeIndent = " ";
String customClassEndMark = @"//******************";
//get file structure
SyntaxTree tree = CSharpSyntaxTree.ParseText(File.ReadAllText(macroFilePath));
//extract method names that are not public static (just macro names)
List<string> macroNames = tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>()
.Where(m => m.Modifiers.ToString() != "public static")
.Select(m => m.Identifier.ToString()).ToList();
// Code that defines a local function "SelectString", which pops up a listbox allowing the user to select a
// string from a number of options:
Func<IList<string>, string, string> SelectString = (IList<string> options, string title) =>
{
var form = new Form();
form.Text = title;
var buttonPanel = new Panel();
buttonPanel.Dock = DockStyle.Bottom;
buttonPanel.Height = 30;
var okButton = new Button() { DialogResult = DialogResult.OK, Text = "OK" };
var cancelButton = new Button() { DialogResult = DialogResult.Cancel, Text = "Cancel", Left = 80 };
var listbox = new ListBox();
listbox.Dock = DockStyle.Fill;
listbox.Items.AddRange(options.ToArray());
listbox.SelectedItem = options[0];
form.Controls.Add(listbox);
form.Controls.Add(buttonPanel);
buttonPanel.Controls.Add(okButton);
buttonPanel.Controls.Add(cancelButton);
var result = form.ShowDialog();
if (result == DialogResult.Cancel) return null;
return listbox.SelectedItem.ToString();
};
//check that macros were found
if (macroNames.Count == 0)
{
Error("No macros found in " + macroFilePath);
return;
}
//let the user select the name of the macro to copy
String select = SelectString(macroNames, "Choose a macro");
//check that indeed one macro was selected
if (select == null)
{
Info("You cancelled!");
return;
}
//get the method
MethodDeclarationSyntax method = tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>()
.First(m => m.Identifier.ToString() == select);
//fix the code
String macroCode = method.Body.ToFullString().Replace("//using", "using").Replace("//#r", "#r");
int firstCurlyBracket = macroCode.IndexOf("{");
int lastCurlyBracket = macroCode.LastIndexOf("}");
macroCode = macroCode.Substring(firstCurlyBracket + 1, lastCurlyBracket - firstCurlyBracket - 1);
//check the custom className
SyntaxTree customClassTree = CSharpSyntaxTree.ParseText(File.ReadAllText(customClassFilePath));
string customClassNamespaceName = customClassTree.GetRoot().DescendantNodes().OfType<NamespaceDeclarationSyntax>().First().Name.ToString();
//check if macro is using custom class
if (macroCode.Contains("using " + customClassNamespaceName))
{
ClassDeclarationSyntax customClass = customClassTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().First();
String customClassCode = customClass.ToString();
int endMarkIndex = customClassCode.IndexOf(customClassEndMark);
//crop the last part and uncomment the closing bracket
customClassCode = customClassCode.Substring(0, endMarkIndex - 1).Replace("//}", "}").Replace("//using", "using").Replace("//#r", "#r");
int hashrFirstMacroCode = Math.Max(macroCode.IndexOf("#r"), 0);
int hashrFirstCustomClass = customClassCode.IndexOf("#r");
if (hashrFirstCustomClass != -1)
{
int hashrLastCustomClass = customClassCode.LastIndexOf("#r");
int endOfHashrCustomClass = customClassCode.IndexOf(Environment.NewLine, hashrLastCustomClass);
string[] hashrLines = customClassCode.Substring(hashrFirstCustomClass, endOfHashrCustomClass - hashrFirstCustomClass).Split('\n');
foreach (String hashrLine in hashrLines)
{
//if #r directive not present
if (!macroCode.Contains(hashrLine))
{
//insert in the code right before the first one
macroCode = macroCode.Substring(0, Math.Max(hashrFirstMacroCode - 1, 0))
+ hashrLine + Environment.NewLine
+ macroCode.Substring(hashrFirstMacroCode);
//update the position of the first #r
hashrFirstMacroCode = Math.Max(customClassCode.IndexOf("#r"), 0);
}
}
//remove #r directives from custom class
customClassCode = customClassCode.Replace(customClassCode.Substring(hashrLastCustomClass, endOfHashrCustomClass - hashrLastCustomClass), "");
}
int usingFirstMacroCode = Math.Max(macroCode.IndexOf("using"), 0);
int usingFirstCustomClass = customClassCode.IndexOf("using");
if (usingFirstCustomClass != -1)
{
int usingLastCustomClass = customClassCode.LastIndexOf("using");
int endOfusingCustomClass = customClassCode.IndexOf(Environment.NewLine, usingLastCustomClass);
string[] usingLines = customClassCode.Substring(usingFirstCustomClass, endOfusingCustomClass - usingFirstCustomClass).Split('\n');
foreach (String usingLine in usingLines)
{
//if #r directive not present
if (!macroCode.Contains(usingLine))
{
//insert in the code right before the first one
macroCode = macroCode.Substring(0, Math.Max(usingFirstMacroCode - 1, 0))
+ usingLine + Environment.NewLine
+ macroCode.Substring(usingFirstMacroCode);
usingFirstMacroCode = Math.Max(macroCode.IndexOf("using"), 0);
}
}
//remove using directives from custom class
customClassCode = customClassCode.Replace(customClassCode.Substring(usingFirstCustomClass, endOfusingCustomClass - usingFirstCustomClass), "");
}
//remove the using directive since it is an in-script custom class
macroCode = macroCode.Replace("using " + customClassNamespaceName, "");
//append custom class to macro
macroCode += customClassCode;
}
string macroCodeClean = "";
string[] macroCodeLines = macroCode.Split('\n');
foreach (string macroCodeLine in macroCodeLines)
{
if (macroCodeLine.StartsWith(codeIndent))
{
macroCodeClean += macroCodeLine.Substring(codeIndent.Length);
}
else
{
macroCodeClean += macroCodeLine;
}
}
//copy the code to the clipboard
Clipboard.SetText(macroCodeClean);`
Where C:\Crea una carpeta\TE-Scripting-in-Visual-Studio\TE Scripts\TE Scripts.cs C:\Crea una carpeta\TE-Scripting-in-Visual-Studio\GeneralFunctions\GeneralFunctions.cs Are path to class definition files in .Net Framework 4.8 projects. (The project can be cloned from this path https://github.com/bernatagulloesbrina/TE-Scripting-in-Visual-Studio)
The code runs fine in TE3 but I only get errors in TE2 I tried referencing by dll file all the extra packages that I had to download to work with Microsoft.CodeAnalysis etc, but even with that I continue to get errors, often pointing to rows where there are only comments, so I'm not sure where the error is.
I tried pointing TE2 to the Roslyn compiler of Visual Studio 2022, but no improvement whatsoever.
Is this fixable in the script by adding more references or is it a limitation of TE2? Any hint's of what I should add to make it work?
Thank you!
Expected behavior I wish it worked in TE2 so more people could use it
Observed behavior At this point only works in TE3
Application specifics
- Tabular Editor 2.18.1
- Semantic Engine used: Power BI Desktop (but not really applicable in this case)
Additional context Would love to present a compatible script on may 22 in PUG Berlin, otherwise will be TE3 only
The line #r "Microsoft.CodeAnalysis"
will not work in TE2, unless you have the DLLs installed in the GAC. Alternatively, make sure that all DLLs for Microsoft.CodeAnalysis, including its dependencies in .NET Framework, are available in TE2's directory of execution, or if you put them in a custom directory, use the fully qualified path of that directory in the #r
statement. The reason it works in TE3, is that TE3 includes these DLLs by default (they are used internally by TE3's script compiler and C# intellisense).
That being said, I don't understand what this script is supposed to accomplish. If you want to reuse code in TE scripts, why not compile a custom dll that you link directly? It is possible to target both .NET Core and .NET Framework from the same C# class library, so that you can produce dll's both for TE2 and TE3.
Thank you Daniel!
I'm trying to streamline development of scripts in Visual Studio and remove all friction points as much as possible. I like in-script classes because they make distribution of the script much easier, but copying each time the script and the class is a bit of a pain, so I decided to make this macro-and-custom-class copier
So if I create a DLL that contains this "macro-copier" script, I only need to refer to this dll from TE2 ? Will the dependencies to code-analysis etc be taken care of? Is there a way to package an all-in dll?
I did try to add the dlls to the Tabular Editor installation folder, but it keeps asking for more and more and after 2 or 3 I don't know what dll is it requesting.
Alternatively, is there a way to make Visual Studio install the packages in the GAC?
Thank you and regards!
I tried to build the DLL, but apparently the dependencies are nowhere to be found
Adding only the dll files that where added as package should be enough?
In my atempts does not look so
hey it works! I realized I had installed the version 7.0, so I modified the version for the 6.0. Then System.Memory was requesting version 4.0.1.2 which is not available in the NuGet Source, but I loaded the lowest available and it worked! So for TE2 people will have to complile the dll to be able to execute script that copies scripts, but it does work!