Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
fc9b20a43This file is pinned to the selected release. Download the original to open it in your design tool.
import { resolve } from "path";
import ScadFile from "./ast/ScadFile";
import ASTPrinter from "./ASTPrinter";
import CodeFile from "./CodeFile";
import ErrorCollector from "./ErrorCollector";
import FormattingConfiguration from "./FormattingConfiguration";
import Lexer from "./Lexer";
import ParsingHelper from "./ParsingHelper";
import ASTScopePopulator from "./semantic/ASTScopePopulator";
import Scope from "./semantic/Scope";
import TokenType from "./TokenType";
describe("ASTPrinter", () => {
function doFormat(source: string) {
let [ast, errorCollector] = ParsingHelper.parseFile(
new CodeFile("<test>", source)
);
errorCollector.throwIfAny();
if (!ast) {
throw new Error("No AST");
}
ast = new ASTScopePopulator(new Scope()).populate(ast) as ScadFile; // populating the scopes should not change anything
return new ASTPrinter(new FormattingConfiguration()).visitScadFile(ast);
}
function injectCommentsBetweenTokens(source: string): [string, string[]] {
const ec = new ErrorCollector();
const lexer = new Lexer(new CodeFile("<test>", source), ec);
const tokens = lexer.scan();
ec.throwIfAny();
let injectId = 0;
const injectedStrings: string[] = [];
let codeWithInjections = "";
for (let i = 0; i < tokens.length; i++) {
const tok = tokens[i];
let shouldInject = true;
// do not inject comments around the use statement since it is illegal
if (
tok.type === TokenType.Use ||
(i != 0 && tokens[i - 1].type === TokenType.Use) ||
tok.type === TokenType.Include ||
(i != 0 && tokens[i - 1].type === TokenType.Include)
) {
shouldInject = false;
}
if (shouldInject) {
const injectionBefore = `/* INJ_${injectId}_B */`;
codeWithInjections += " " + injectionBefore + " ";
injectedStrings.push(injectionBefore);
injectId++;
}
codeWithInjections += tok.lexeme;
if (shouldInject) {
const injectionAfter = `/* INJ_${injectId}_A */`;
codeWithInjections += " " + injectionAfter + " ";
injectedStrings.push(injectionAfter);
injectId++;
}
}
return [codeWithInjections, injectedStrings];
}
/**
* Inserts comments between all tokens and then formats the code,
* then checks if the comments are still there
* @param source the source code to check
*/
function doPreserveTest(source: string) {
const [codeWithInjections, injectedStrings] =
injectCommentsBetweenTokens(source);
// we do two passes, one with all the comments and another one with the problematic ones, this is where we throw errors
const formatted = doFormat(codeWithInjections);
let problematicCode = codeWithInjections;
let problematicInjections: string[] = [];
for (const inj of injectedStrings) {
if (formatted.indexOf(inj) !== -1) {
// the injection exists in the formatted code se we remove it
problematicCode = problematicCode.replace(inj, " ");
} else {
problematicInjections.push(inj);
}
}
const problematicFormatted = doFormat(problematicCode);
if (problematicInjections.length > 0) console.log(problematicCode);
for (const prob of problematicInjections) {
expect(problematicFormatted).toEqual(expect.stringContaining(prob));
}
}
test("it preserves all comments in a file with all the syntactical elements", () => {
doPreserveTest(`
use <ddd>
include <gfff>
assignment = 123; the_same_line = 8;
function ddd(argv = 10, second = !true) = (10 + 20) * 10;
ybyby = x > 10 ? let(v = 200) doSomething() : assert(x = 20) echo("nothing") 5;
arr = [20, if(true) each [20:50:30] else [808][0].x];
compre = [for(a = [rang1, 2, 3]) let(x = a + 1) [sin(a)],];
fun = function(a, b) a * b(13);
alpha = undef || beta && gamma;
module the_mod(arg1, arg2=2-2) {
echo( [for (a = 0, b = 1;a < 5;a = a + 1, b = b + 2) [ a, b * b ] ] );
if(yeah == true) {
;
} else {
}
}
`);
});
test("it preserves all comments nearby an use statement", () => {
doPreserveTest(`
use <ddd>
`);
});
test("it preserves all comments nearby vectors", () => {
doPreserveTest(`
a = [d,];
`);
doPreserveTest(`
a = [d,a,b];
`);
doPreserveTest(`
a = [d];
`);
});
test("it preserves all comments nearby list comprehensions with a for", () => {
doPreserveTest(`
compre = [for(a = [rang1, 2, 3]) x ];
`);
});
test("it preserves all comments nearby exponentiation operators", () => {
doPreserveTest(`
expo = 3 * 2 ^ 8;
`);
});
test("it preserves all comments nearby module modifiers", () => {
doPreserveTest(`
% asdf();
* ffff();
# xD();
! bangbang();
`);
});
test("it preserves comments and tokens nearby anonymous functions", () => {
doPreserveTest(`
func = function (x) x * x;
echo(func(5)); // ECHO: 25
a = 1;
selector = function (which)
which == "add"
? function (x) x + x + a
: function (x) x * x + a;
echo(selector("add")); // ECHO: function(x) ((x + x) + a)
echo(selector("add")(5)); // ECHO: 11
echo(selector("mul")); // ECHO: function(x) ((x * x) + a)
echo(selector("mul")(5)); // ECHO: 26
`);
});
test.skip("it preserves comments in ddd.scad", async () => {
doPreserveTest(
(await CodeFile.load(resolve(__dirname, "testdata/ddd.scad"))).code
);
});
test("it does not add a space between the closing paren and semicolon in an empty module", () => {
const f = doFormat(`module asdf();`);
expect(f).not.toStrictEqual(expect.stringContaining(") ;"));
});
test("does not cut off end chevron of an use statement", () => {
const f = doFormat(`use <xD>`);
expect(f).toStrictEqual(expect.stringContaining("<xD>"));
});
test("does not cut off modifiers in module instantations", () => {
const f = doFormat(`
translate([0, -100, -50])
% import("relay_din_mount.stl");
`);
expect(f).toStrictEqual(expect.stringContaining("%"));
});
test("does not introduce newlines before else branches", () => {
const f = doFormat(`
if(true) {
doSomething();
} else {
doSomethingElse();
}
`);
expect(f).toStrictEqual(expect.stringContaining("} else {"));
});
test("does not introduce newlines before else if branches", () => {
const f = doFormat(`
if(true) {
doSomething();
} else if(false) {
doSomethingElse();
}
`);
expect(f).toStrictEqual(expect.stringContaining("} else if(false) {"));
});
test("preserves exponentiation operators", () => {
const f = doFormat(`
expo = 3 * 2 ^ 8;
`);
expect(f).toStrictEqual(expect.stringContaining("^"));
});
test("formats 'for' comprehensions without variable names", () => {
expect(doFormat(`x = [for([0:3]) 1];`)).toStrictEqual(
`x = [for([0 : 3]) 1];\n`
);
});
test("does not add a newline after a semicolon in an assignment if a comment follows it", () => {
const f = doFormat(`
enum_value="a"; // [a:ayyyy, b:beee, c:seeee]
`);
expect(f).toStrictEqual(`
enum_value = "a"; // [a:ayyyy, b:beee, c:seeee]
`);
});
test("does keep a newline after a semi-colon in an assignment if the user wants it", () => {
const f = doFormat(
`
enum_value="a";
// [a:ayyyy, b:beee, c:seeee]
`
);
expect(f).toStrictEqual(`
enum_value = "a";
// [a:ayyyy, b:beee, c:seeee]
`);
});
it("preserves comments in 'for' list comprehensions", () => {
doPreserveTest(`
x = [for(a = [0:3]) 1];
`);
});
it("preserves comments in 'for' list comprehensions without variable names", () => {
doPreserveTest(`
x = [for([0:3]) 1];
x = [for("abc") 1];
x = [for(alpha) 1];
`);
});
it("correctly handles comments after chained module instantiations", () => {
const formatted = doFormat(`
$fn=4;// a comment
translate([-5,-5,-10]) scale([1,1,2]) // another comment
cube([10,10,10]);
`);
expect(formatted).toStrictEqual(`
$fn = 4; // a comment
translate([-5, -5, -10])
scale([1, 1, 2]) // another comment
cube([10, 10, 10]);
`);
});
it("it adds spaces between module chained instantiations in one line", () => {
const formatted = doFormat(`
scale()cube();
`);
expect(formatted).toStrictEqual(`
scale() cube();
`);
});
it("properly breaks apart long module instantiations", () => {
const formatted = doFormat(`
scale() translate() translate() translate() rotate() cube();
`);
expect(formatted).toStrictEqual(`
scale()
translate()
translate()
translate()
rotate()
cube();
`);
});
it("preserves comments in varius module instantiations", () => {
doPreserveTest(`
scale() translate() translate() translate() rotate() cube();
scale()
translate()
translate()
translate()
rotate()
cube();
scale()cube();
$fn = 4; // a comment
translate([-5, -5, -10])
scale([1, 1, 2]) // another comment
cube([10, 10, 10]);
`);
});
test("It preserves comments in files doing division (harness test)", async () => {
doPreserveTest(`translate([distance1-distance1/2,8.5,0]);`);
});
it("doesn't break code with comment after ending brace", async () => {
const formatted = doFormat(`
{
{
} //end if
}`);
let [ast, errorCollector] = ParsingHelper.parseFile(
new CodeFile("<test>", formatted)
);
errorCollector.throwIfAny();
});
it("Adds newlines between stacked closing braces", async () => {
const formatted = doFormat(`
{
{
}}`);
expect(formatted).not.toContain("}}");
});
it("does not add newlines near the else keyword in ifs", async () => {
const formatted = doFormat(`
if(true) {
} else {
}`);
expect(formatted).toContain("} else {");
});
it("doesn't break code with comment after ending brace of an else statement", async () => {
const formatted = doFormat(`
if(true) {
} // hello
else
// hello
{
}`);
let [ast, errorCollector] = ParsingHelper.parseFile(
new CodeFile("<test>", formatted)
);
errorCollector.throwIfAny();
});
});