Skip to content

Commit

Permalink
fix: constructor validation on build (#226)
Browse files Browse the repository at this point in the history
* fix: constructor validation on build

* Update unordered-map.js

* feat: constructor checker

* feat: constructor validation

* fix: resolve conflicts

* fix: resolve conflicts

* fix: code cleaning

* fix: code cleaning

* fix: code cleaning

* fix: lint

* fix: tests

* fix: empty state contract

* fix: update contract valdiation to handle clean-state example

* linter fix

* constructor validation moved to utils

* constructor test contracts renamed, typos fixed

* redandunt comment deleted

* Error messages changed

* log comments added to contract validation

* yarn build

* cli/cli.js deleted

* contract validation added to the main pipeline

* contract validation rewrittent with TS

* signal used in contract validation, tests rewritten

* contract validation doc added

Co-authored-by: Serhii Volovyk <SergeyVolovyk@gmail.com>
  • Loading branch information
osalkanovic and volovyks authored Oct 26, 2022
1 parent ef1a7b2 commit 04a0b6d
Show file tree
Hide file tree
Showing 16 changed files with 2,099 additions and 1,101 deletions.
3 changes: 2 additions & 1 deletion examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@types/lodash-es": "^4.17.6",
"ava": "^4.2.0",
"near-workspaces": "3.2.1",
"typescript": "^4.7.4"
"typescript": "^4.7.4",
"npm-run-all": "^4.1.5"
}
}
1,434 changes: 927 additions & 507 deletions examples/yarn.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion lib/cli/cli.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions lib/cli/utils.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions lib/cli/utils.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
"@types/eslint": "^8.4.6",
"@types/node": "^17.0.38",
"@types/rollup": "^0.54.0",
"chalk": "^5.1.0",
"ts-morph": "^16.0.0",
"@types/signale": "^1.4.4",
"@typescript-eslint/eslint-plugin": "^5.37.0",
"@typescript-eslint/parser": "^5.37.0",
Expand Down
5 changes: 4 additions & 1 deletion src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { rollup } from "rollup";
import { Command } from "commander";
import signal from "signale";

import { executeCommand } from "./utils.js";
import { executeCommand, validateContract } from "./utils.js";

const { Signale } = signal;
const PROJECT_DIR = process.cwd();
Expand Down Expand Up @@ -66,6 +66,9 @@ export async function buildCom(
signale.await(`Creating ${TARGET_DIR} directory...`);
await executeCommand(`mkdir -p ${TARGET_DIR}`, verbose);

signal.await(`Validatig ${source} contract...`);
await validateContract(source);

signale.await(`Creating ${source} file with Rollup...`);
await createJsFileWithRullup(source, ROLLUP_TARGET, verbose);

Expand Down
48 changes: 48 additions & 0 deletions src/cli/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import childProcess from "child_process";
import { promisify } from "util";
import signal from "signale"
import { ClassDeclaration, ClassDeclarationStructure, ConstructorDeclaration, OptionalKind, Project, PropertyDeclarationStructure, SourceFile } from "ts-morph";
import chalk from "chalk";
import signale from "signale";

const {Signale} = signal;

Expand Down Expand Up @@ -37,3 +40,48 @@ export async function executeCommand(
export async function download(url: string, verbose = false) {
await executeCommand(`curl -LOf ${url}`, verbose);
}

const UNINITIALIZED_PARAMETERS_ERROR = "All parameters must be initialized in the constructor. Uninitialized parameters:";

/**
* Validates the contract by checking that all parameters are initialized in the constructor. Works only for contracts written in TypeScript.
* @param contractPath Path to the contract.
**/
export async function validateContract(contractPath: string): Promise<boolean> {
const project: Project = new Project();
project.addSourceFilesAtPaths(contractPath);
const sourceFile: SourceFile = project.getSourceFile(contractPath);
const classDeclarations: ClassDeclaration[] = sourceFile.getClasses();
for (const classDeclaration of classDeclarations) {
const classStructure: ClassDeclarationStructure = classDeclaration.getStructure();
const { decorators, properties } = classStructure;
const hasNearBindgen: boolean = decorators.find(
(decorator) => decorator.name === "NearBindgen"
) ? true : false;
if (hasNearBindgen) {
const constructors: ConstructorDeclaration[] = classDeclaration.getConstructors();
const hasConstructor = constructors.length > 0;
const propertiesToBeInited: OptionalKind<PropertyDeclarationStructure>[] = properties.filter((p) => !p.initializer);
if (!hasConstructor && propertiesToBeInited.length === 0) {
return true;
}
if (!hasConstructor && propertiesToBeInited.length > 0) {
signale.error(chalk.redBright(`${UNINITIALIZED_PARAMETERS_ERROR} ${propertiesToBeInited.map((p) => p.name)}`));
process.exit(1);
}
const constructor: ConstructorDeclaration = constructors[0];
const constructorContent: string = constructor.getText();
const nonInitedProperties: string[] = [];
for (const property of propertiesToBeInited) {
if (!constructorContent.includes(`this.${property.name}`)) {
nonInitedProperties.push(property.name);
}
}
if (nonInitedProperties.length > 0) {
signale.error(chalk.redBright(`${UNINITIALIZED_PARAMETERS_ERROR} ${nonInitedProperties.join(", ")}`));
process.exit(1);
}
}
}
return true;
}
39 changes: 39 additions & 0 deletions tests/__tests__/constructor_validation.ava.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import test from "ava";
import { execSync } from "child_process";

const BUILD_FAILURE_ERROR_CODE = 1;

test("should not throw error, constructor is correctly initialized", async (t) => {
t.notThrows(() => {
execSync(
"near-sdk-js build src/constructor-validation/all-parameters-set-in-constructor.ts build/all-parameters-set-in-constructor.wasm"
);
});
});

test("should throw error, name is not inited", async (t) => {
const error = t.throws(() => {
execSync(
"near-sdk-js build src/constructor-validation/1-parameter-not-set-in-constructor.ts build/1-parameter-not-set-in-constructor.wasm"
);
});
t.is(error.status, BUILD_FAILURE_ERROR_CODE);
});

test("should throw error, construcor is empty", async (t) => {
const error = t.throws(() => {
execSync(
"near-sdk-js build src/constructor-validation/no-parameters-set-in-constructor.ts build/no-parameters-set-in-constructor.wasm"
);
});
t.is(error.status, BUILD_FAILURE_ERROR_CODE);
});

test("should throw error, construcor is not declared", async (t) => {
const error = t.throws(() => {
execSync(
"near-sdk-js build src/constructor-validation/no-constructor.ts build/no-constructor.wasm"
);
});
t.is(error.status, BUILD_FAILURE_ERROR_CODE);
});
4 changes: 3 additions & 1 deletion tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,15 @@
"test:bigint-serialization": "ava __tests__/test-bigint-serialization.ava.js",
"test:date-serialization": "ava __tests__/test-date-serialization.ava.js",
"test:serialization": "ava __tests__/test-serialization.ava.js",
"test:constructor-validation": "ava __tests__/constructor_validation.ava.js",
"test:middlewares": "ava __tests__/test-middlewares.ava.js"
},
"author": "Near Inc <hello@nearprotocol.com>",
"license": "Apache-2.0",
"devDependencies": {
"ava": "^4.2.0",
"near-workspaces": "3.2.1",
"typescript": "^4.7.4"
"typescript": "^4.7.4",
"npm-run-all": "^4.1.5"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { NearBindgen, LookupMap } from "near-sdk-js";

@NearBindgen({})
export class ConstructorValidation {
map: LookupMap<string>;
name: string;
constructor() {
this.map = new LookupMap<string>("a");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { NearBindgen, LookupMap, call } from "near-sdk-js";

@NearBindgen({})
export class ConstructorValidation {
map: LookupMap<string>;
name: string;
constructor() {
this.map = new LookupMap<string>("a");
this.name = "";
}

@call({})
get() {
return { status: "ok" };
}
}
7 changes: 7 additions & 0 deletions tests/src/constructor-validation/no-constructor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { NearBindgen, LookupMap } from "near-sdk-js";

@NearBindgen({})
export class ConstructorValidation {
map: LookupMap<string>;
name: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { NearBindgen, LookupMap } from "near-sdk-js";

@NearBindgen({})
export class ConstructorValidation {
map: LookupMap<string>;
name: string;
constructor() {
//
}
}
Loading

0 comments on commit 04a0b6d

Please sign in to comment.