How to track SyntaxNodes across Workspace.TryApplyChanges() - roslyn

I'd like to track SyntaxNodes and SyntaxTrivias across
different versions of a Solution/Workspace.
I tried annotating some nodes with SyntaxAnnotations.
This works well as long as I don't update the workspace.
Calling Workspace.TryApplyChanges (successfully) seems to remove
all SyntaxAnnotations.
This surprised me. Why does this happen?
How can I track SyntaxNodes across workspace updates?
Example code follows:
var workspace = new AdhocWorkspace();
var project = workspace.AddProject("TestProject", LanguageNames.CSharp);
var klass = SyntaxFactory
.ClassDeclaration("Klass")
.WithAdditionalAnnotations(new SyntaxAnnotation("Foo"));
var compUnit = SyntaxFactory.CompilationUnit().AddMembers(klass);
var document = project.AddDocument("TestFile.cs", compUnit);
var docId = document.Id;
var solution = document.Project.Solution;
var root1 = document.GetSyntaxRootAsync().Result;
var klass1 = root1.GetAnnotatedNodes("Foo").FirstOrDefault();
var eq1 = klass1.IsEquivalentTo(klass); // returns true
var apply = workspace.TryApplyChanges(solution); // returns true
var root2 = workspace.CurrentSolution.GetDocument(docId).GetSyntaxRootAsync().Result;
var klass2 = root2.GetAnnotatedNodes("Foo").FirstOrDefault(); // returns null, why?

This happens because TryApplyChanges doesn't actually re-use your nodes as is. Instead it "replays" the same changes as textual changes to the actual solution, and then let's the parser re-parse.
This happens for a few reasons:
To avoid having annotations pile up over time in the trees and interfere with each other (consider something like that formatting or rename annotations used in CodeFixes still being present after the fix was applied).
To protect against trees that don't round-trip from showing up in CurrentSolution. It is possible to construct trees that the parser would never generate (consider changing operator precedence for example).
To ensure the changes are actually applied, requires changing the original representation - the files on disk or the text buffers in memory, not just using the new trees in the workspace.
You could consider using something like the SyntaxPath type from the Roslyn sources to try to find an equivalent node.

Related

Been trying to automate the Find and Replace in Google Sheets but did not work

My sheet is a query-sheet from database. Some of them contain html hex-code color which I need to manually use edit>Find and Replace every time it is refreshed.
I am very new to Google App Script and been trying to use the following code:
function Clearcode() {
var lookupone = new RegExp(/{color:#.{7}/);
var rep = "";
var spreadSheet = SpreadsheetApp.getActive();
var querySheet = spreadSheet.getSheetByName("QUERY");
var lastRow = querySheet.getLastRow();
var lastColumn = querySheet.getLastColumn();
var data = querySheet.getRange(2, 1, lastRow-1, lastColumn).getValues();
var textfinder = querySheet.createTextFinder(lookupone);
var found = textfinder.replaceAllWith(rep);
return (found);
}
Yet, when I run this function in the sheet it did not work. Any thought?
P.S. I planned to eliminated "[color]" part of the hex-code as well by create the similar function.
P.S.2 I have attached a snapshot of a table as you requested. The red line is just for confidentiality of the data. Below the line is just a normal text.
Pay attention to types!
CreateTextFinder accepts String as argument NOT a regexp object.
To use strings as regular expressions, useRegularExpressions needs to be set to true
querySheet.createTextFinder("\\{color:#?.{0,6}\\}")//only 6 characters
.useRegularExpressions(true)
.replaceAllWith("")

How to unit test a method that is having multiple object creation in switch statement? How to Mock them?

Another question is if there is any better way to write this method?
Public decimal CalculateTotalPrice(List<product> items)
{
decimal totalPrice = 0.m;
foreach(Product p in items)
{
if(p.Offer == "")
calc = new DefaultCalc();
else if(p.Offer == "BuyOneGetOneFree")
calc = new BuyOneGetOneFreeCalc();
else if(p.Offer == "ThreeInPriceOfTwo")
calc = new ThreeInPriceOfTwoCalc()
totalPrice += calc.Calculate(p.Quantity, p.UnitPrice);
}
return totalPrice;
}
You should probably review Polly Want a Message, by Sandi Metz
How to unit test a method that is having multiple object creation in switch statement?
An important thing to notice here is that the switch statement is an implementation detail. From the point of view of the caller, this thing is just a function
Public decimal CalculateTotalPrice(List<product> items);
If the pricing computations are fixed, you can just use the usual example based tests:
assertEquals(expectedPrice, CalculateTotalPrice(items));
But if they aren't fixed, you can still do testing based on the properties of the method. Scott Wlaschin has a really good introduction to property based testing. Based on the logic you show here, there are some things we can promise about prices, without knowing anything about the strategies in use
the price should always be greater than zero.
the price of a list of items is the same as the sum of the prices of the individual items.
if there is any better way to write this method?
You might separate choosing the pricing strategy from using the strategy. As Sandi notes, that sort of construct often shows up in more than once place.
foreach(Product p in items)
{
calc = pricing(p.Offer);
totalPrice += calc.Calculate(p.Quantity, p.UnitPrice);
}
"pricing" would then become something that you pass into this function (either as an argument, or as a dependency).
In effect, you would end up with three different kinds of test.
Checks that pricing returns the right pricing strategy for each offer.
Checks that each strategy performs its own calculation correctly.
Checks that CalculateTotalPrice compute the sum correctly.
Personally, I prefer to treat the test subject as a single large black box, but there are good counter arguments. Horses for courses.
Constructors can not be mocked (at least with free mocking frameworks).
Write tests without mocking as far as your tests run fast and test case setup is not very very complicated.
In your particular case you should be able to write tests without mocking.
Prepare data
var products = new List<Product>
{
new Product { Quantity = 10, UnitPrice = 5.0m, Offer = "" },
new Product { Quantity = 2, UnitPrice = 3.0m , Offer = "BuyOneGetOneFree" },
new Product { Quantity = 3, UnitPrice = 2.0m , Offer = "ThreeInPriceOfTwo" },
}
// prepare expected total
var expected = 57.0m; // 10 * 50.0 + 1 * 3.0 + 2 * 2.0
// Run the test
var actual = CalculateTotalPrice(products);
actual.Should().Be(expected); // pass Ok.
With this approach tests will not depend on implementation details.
You will be able to freely play with designs without rewriting tests every time you change your implementation logic.
The other answers are technically fine, but I would suggest one thing:
if(p.Offer == "")
calc = new DefaultCalc();
else if(p.Offer == "BuyOneGetOneFree")
calc = new BuyOneGetOneFreeCalc();
else if(p.Offer == "ThreeInPriceOfTwo")
calc = new ThreeInPriceOfTwoCalc()
should absolutely go into its own method/scope/whatever.
You are mapping a string to a specific calculator. That should happen in one place, and one place only. You see, first you do that here. Then some method method comes around that needs the same mapping. So you start duplicating.

Increasing Variables

So I am writing a program that will need to increase some variables by the amount that the user types. I thought a code like this would work:
var = 0
increase = input("Increase by: ")
var + increase
print(var)
But it just says that var is 0. I even tried making the input type int, int(input(""))
Still didn't work. Why won't it increase the variable?
Keep in mind, I am using Python 2.7
EDIT It was solved! I just made it var += increase
You need to understand the difference between a statement and an assignment.
You're making the statement var + increase, but its result is never used. You're basically asking the computer to evaluate it, but there is no side-effect (like printing it to the screen, sending it over the network or putting it somewhere in memory)
You can use the result of your statement in many ways, for example, you can print it directly:
print var + increase
Or you can assign it to a variable
var = var + increase
Even better, to a NEW variable:
result = var + increase
Actually, in python, if you make just a statement, it is not entirely lost, it's kept in a magic variable called "ans" which is denoted by an underscore (_).
So you could do
var + increase
print(_)
you're missing the setting of var. When you add var + increase it needs to be
var=var+increase.
return var or print(var)
basically, you're adding var to increase without doing anything with the resulting sum.
You need to update var. All you are doing in line 3 is assigning var + increase to ans. Try doing this:
var = 0
increase = input("Increase by: ")
var = var + increase
print var

CouchDB View - filter keys before grouping

I have a CouchDB database which has documents with the following format:
{ createdBy: 'userId', at: 123456, type: 'action_type' }
I want to write a view that will give me how many actions of each type were created by which user. I was able to do that creating a view that does this:
emit([doc.createdBy, doc.type, doc.at], 1);
With the reduce function "sum" and consuming the view in this way:
/_design/userActionsDoc/_view/userActions?group_level=2
this returns a result with rows just in the way I want:
"rows":[ {"key":["userId","ACTION_1"],"value":20}, ...
the problem is that now I want to filter the results for a given time period. So I want to have the exact same information but only considering actions which happened within a given time period.
I can filter the documents by "at" if I emit the fields in a different order.
?group_level=3&startkey=[149328316160]&endkey=[1493283161647,{},{}]
emit([doc.at, doc.type, doc.createdBy], 1);
but then I won't get the results grouped by userId and actionType. Is there a way to have both? Maybe writing my own reduce function?
I feel your pain. I have done two different things in the past to attempt to solve similar issues.
The first pattern is a pain and may work great or may not work at all. I've experienced both. Your map function looks something like this:
function(doc) {
var obj = {};
obj[doc.createdBy] = {};
obj[doc.createdBy][doc.type] = 1;
emit(doc.at, obj);
// Ignore this for now
// emit(doc.at, JSON.stringify(obj));
}
Then your reduce function looks like this:
function(key, values, rereduce) {
var output = {};
values.forEach(function(v) {
// Ignore this for now
// v = JSON.parse(v);
for (var user in v) {
for (var action in v[user]) {
output[user][action] = (output[user][action] || 0) + v[user][action];
}
}
});
return output;
// Ignore this for now
// return JSON.stringify(output);
}
With large datasets, this usually results in a couch error stating that your reduce function is not shrinking fast enough. In that case, you may be able to stringify/parse the objects as shown in the "ignore" comments in the code.
The reasoning behind this is that couchdb ultimately wants you to output a simple object like a string or integer in a reduce function. In my experience, it doesn't seem to matter that the string gets longer, as long as it remains a string. If you output an object, at some point the function errors because you have added too many props to that object.
The second pattern is potentially better, but requires that your time periods are "defined" ahead of time. If your time period requirements can be locked down to a specific year, specific month, day, quarter, etc. You just emit multiple times in your map function. Below I assume the at property is epoch milliseconds, or at least something that the date constructor can accurately parse.
function(doc) {
var time_key;
var my_date = new Date(doc.at);
//// Used for filtering results in a given year
//// e.g. startkey=["2017"]&endkey=["2017",{}]
time_key = my_date.toISOString().substr(0,4);
emit([time_key, doc.createdBy, doc.type], 1);
//// Used for filtering results in a given month
//// e.g. startkey=["2017-01"]&endkey=["2017-01",{}]
time_key = my_date.toISOString().substr(0,7);
emit([time_key, doc.createdBy, doc.type], 1);
//// Used for filtering results in a given quarter
//// e.g. startkey=["2017Q1"]&endkey=["2017Q1",{}]
time_key = my_date.toISOString().substr(0,4) + 'Q' + Math.floor(my_date.getMonth()/3).toString();
emit([time_key, doc.createdBy, doc.type], 1);
}
Then, your reduce function is the same as in your original. Essentially you're just trying to define a constant value for the first item in your key that corresponds to a defined time period. Works well for business reporting, but not so much for allowing for flexible time periods.

GoogleSheet script editor - onEdit event with conditions / if statement

guys!
I'm new to this website and also not good with coding. So I would really appreciate some help.
Right now I'm in need of a specific code to make a google sheet work perfectly.
To further explain:
I have a google sheet that a few information will be input by other co-workers. What I need is a code that will register the date in a specific cell and by whom the input was made on another cell.
So far this is what I have:
function onEdit(event) {
var sheet = event.source.getSheetByName("Input");
// Note: actRng = return the last cell of the row modified
var actRng = event.source.getActiveRange();
var index = actRng.getRowIndex();
var cindex = actRng.getColumnIndex();
// Note: date = return date
// Note: user = return the user email
var userCell = sheet.getRange(index,14);
var dateCell = sheet.getRange(index,2);
var inputdate = Utilities.formatDate(new Date(), "GMT+0200", "yyyy-MM-dd");
// Note(with hour): var inputdate = Utilities.formatDate(new Date(), "GMT+0200", "yy-MM-dd HH:mm");
//var user = event.user; // Note: event.user will not give you collaborator's Id
var user = Session.getEffectiveUser();
// Note: setValue = Insert in the cell the date when this row was modified
if (userCell.Value == null) {
userCell.setValue(user);
dateCell.setValue(inputdate)
}
}
My main problems/questions are:
I don't exactly need the last modifier, but the person who first input info on the cells. Therefore I tried that last IF (If the cell that is supposed to have the last modifier e-mail is blank, it means that nobody changed that row before, so the code should add the user on the userCell), although it is not working since every change I make it ignores the verification.
I also want to add that the event will only happen if you add values, if you delete them, nothing happens. (so far even when I delete cells, it counts as modification)
Most of the sheet is protected to avoid that people by accident erase some of the formulas, so the cells that this code changes are also protected. Is there a way to make the code bypass cell protection?
Please, help me identify what I'm doing wrong and hopefully I'll get this working perfectly! Thanks for the help !
If you want to prevent the script from firing when a cell is deleted, try:
var editedCell = SpreadsheetApp.getActiveSheet().getRange(e.range.getRow(), e.range.getColumn());
if (editedCell == "") {
return;
}
I would change Session.getEffectiveUser() to session.getActiveUser().
The last if statement is unnecessary. You want whoever most recently edited the field to be identified, along with the date.