From cc7a64a593a48f64dc74e27a12196104dfbf9b98 Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Fri, 18 Mar 2022 16:22:21 +0100 Subject: [PATCH] feat: support symlinked packages --- .gitignore | 2 + src/transform-inline/transform-node.ts | 4 +- src/transform-inline/transformer.ts | 3 +- src/transform-inline/visitor-context.d.ts | 1 + src/transform-inline/visitor-utils.ts | 10 +- test-fixtures/issue-104.js | 152 ++++++++++++++++++++++ test/issue-16.ts | 21 +-- test/issue-27.ts | 5 +- test/issue-3.ts | 5 +- test/issue-31.ts | 3 +- test/issue-50.ts | 10 +- test/issue-52.ts | 15 ++- 12 files changed, 204 insertions(+), 27 deletions(-) create mode 100644 test-fixtures/issue-104.js diff --git a/.gitignore b/.gitignore index 8c440c2..45685d2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ node_modules/ lib/ test/*.js .idea +*.tgz +.vscode/ \ No newline at end of file diff --git a/src/transform-inline/transform-node.ts b/src/transform-inline/transform-node.ts index a78ec30..d9ae9b7 100644 --- a/src/transform-inline/transform-node.ts +++ b/src/transform-inline/transform-node.ts @@ -58,7 +58,7 @@ function transformDecorator(node: ts.Decorator, parameterType: ts.Type, paramete if ( signature !== undefined && signature.declaration !== undefined - && path.resolve(signature.declaration.getSourceFile().fileName) === path.resolve(path.join(__dirname, '..', '..', 'index.d.ts')) + && VisitorUtils.getCanonicalPath(path.resolve(signature.declaration.getSourceFile().fileName), visitorContext) === path.resolve(path.join(__dirname, '..', '..', 'index.d.ts')) && node.expression.arguments.length <= 1 ) { const arrowFunction: ts.Expression = createArrowFunction(parameterType, parameterName, optional, visitorContext); @@ -102,7 +102,7 @@ export function transformNode(node: ts.Node, visitorContext: PartialVisitorConte if ( signature !== undefined && signature.declaration !== undefined - && path.resolve(signature.declaration.getSourceFile().fileName) === path.resolve(path.join(__dirname, '..', '..', 'index.d.ts')) + && VisitorUtils.getCanonicalPath(path.resolve(signature.declaration.getSourceFile().fileName), visitorContext) === path.resolve(path.join(__dirname, '..', '..', 'index.d.ts')) && node.typeArguments !== undefined && node.typeArguments.length === 1 ) { diff --git a/src/transform-inline/transformer.ts b/src/transform-inline/transformer.ts index d604101..2580ed6 100644 --- a/src/transform-inline/transformer.ts +++ b/src/transform-inline/transformer.ts @@ -46,7 +46,8 @@ export default function transformer(program: ts.Program, options?: { [Key: strin emitDetailedErrors: getEmitDetailedErrors(options) }, typeMapperStack: [], - previousTypeReference: null + previousTypeReference: null, + canonicalPaths: new Map() }; return (context: ts.TransformationContext) => (file: ts.SourceFile) => transformNodeAndChildren(file, program, context, visitorContext); } diff --git a/src/transform-inline/visitor-context.d.ts b/src/transform-inline/visitor-context.d.ts index 1fd2199..0ced7cb 100644 --- a/src/transform-inline/visitor-context.d.ts +++ b/src/transform-inline/visitor-context.d.ts @@ -24,4 +24,5 @@ export interface PartialVisitorContext { options: Options; typeMapperStack: Map[]; previousTypeReference: ts.Type | null; + canonicalPaths: Map; } diff --git a/src/transform-inline/visitor-utils.ts b/src/transform-inline/visitor-utils.ts index 8db6555..f2f647b 100644 --- a/src/transform-inline/visitor-utils.ts +++ b/src/transform-inline/visitor-utils.ts @@ -1,7 +1,8 @@ import * as ts from 'typescript'; +import * as fs from 'fs'; import { ModifierFlags } from 'typescript'; import * as tsutils from 'tsutils/typeguard/3.0'; -import { VisitorContext } from './visitor-context'; +import { PartialVisitorContext, VisitorContext } from './visitor-context'; import { Reason } from '../../index'; /** @@ -731,3 +732,10 @@ export function getIntrinsicName(type: ts.Type): string | undefined { // Using internal TypeScript API, hacky. return (type as { intrinsicName?: string }).intrinsicName; } + +export function getCanonicalPath(path: string, context: PartialVisitorContext): string { + if (!context.canonicalPaths.has(path)) { + context.canonicalPaths.set(path, fs.realpathSync(path)); + } + return context.canonicalPaths.get(path)!; +} \ No newline at end of file diff --git a/test-fixtures/issue-104.js b/test-fixtures/issue-104.js new file mode 100644 index 0000000..df90f2c --- /dev/null +++ b/test-fixtures/issue-104.js @@ -0,0 +1,152 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AsyncMethods = void 0; +const index_1 = require("../index"); +let AsyncMethods = class AsyncMethods { + asyncMethod(body) { + return __awaiter(this, void 0, void 0, function* () { + return true; + }); + } + asyncMethodNoExplicitReturn(body) { + return __awaiter(this, void 0, void 0, function* () { + return true; + }); + } + promiseReturnMethod(body) { + return Promise.resolve(true); + } + asyncOverride(body) { + return __awaiter(this, void 0, void 0, function* () { + return true; + }); + } + promiseOrOtherReturnMethod(body) { + return Promise.resolve(true); + } +}; +__decorate([ + __param(0, index_1.AssertType(object => { var path = ["body"]; function _string(object) { ; if (typeof object !== "string") + return { message: "validation failed at " + path.join(".") + ": expected a string", path: path.slice(), reason: { type: "string" } }; + else + return null; } function _0(object) { ; if (typeof object !== "object" || object === null || Array.isArray(object)) + return { message: "validation failed at " + path.join(".") + ": expected an object", path: path.slice(), reason: { type: "object" } }; { + if ("test" in object) { + path.push("test"); + var error = _string(object["test"]); + path.pop(); + if (error) + return error; + } + else + return { message: "validation failed at " + path.join(".") + ": expected 'test' in object", path: path.slice(), reason: { type: "missing-property", property: "test" } }; + } return null; } return _0(object); })), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], AsyncMethods.prototype, "asyncMethod", null); +__decorate([ + __param(0, index_1.AssertType(object => { var path = ["body"]; function _string(object) { ; if (typeof object !== "string") + return { message: "validation failed at " + path.join(".") + ": expected a string", path: path.slice(), reason: { type: "string" } }; + else + return null; } function _0(object) { ; if (typeof object !== "object" || object === null || Array.isArray(object)) + return { message: "validation failed at " + path.join(".") + ": expected an object", path: path.slice(), reason: { type: "object" } }; { + if ("test" in object) { + path.push("test"); + var error = _string(object["test"]); + path.pop(); + if (error) + return error; + } + else + return { message: "validation failed at " + path.join(".") + ": expected 'test' in object", path: path.slice(), reason: { type: "missing-property", property: "test" } }; + } return null; } return _0(object); })), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], AsyncMethods.prototype, "asyncMethodNoExplicitReturn", null); +__decorate([ + __param(0, index_1.AssertType(object => { var path = ["body"]; function _string(object) { ; if (typeof object !== "string") + return { message: "validation failed at " + path.join(".") + ": expected a string", path: path.slice(), reason: { type: "string" } }; + else + return null; } function _0(object) { ; if (typeof object !== "object" || object === null || Array.isArray(object)) + return { message: "validation failed at " + path.join(".") + ": expected an object", path: path.slice(), reason: { type: "object" } }; { + if ("test" in object) { + path.push("test"); + var error = _string(object["test"]); + path.pop(); + if (error) + return error; + } + else + return { message: "validation failed at " + path.join(".") + ": expected 'test' in object", path: path.slice(), reason: { type: "missing-property", property: "test" } }; + } return null; } return _0(object); })), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], AsyncMethods.prototype, "promiseReturnMethod", null); +__decorate([ + __param(0, index_1.AssertType(object => { var path = ["body"]; function _string(object) { ; if (typeof object !== "string") + return { message: "validation failed at " + path.join(".") + ": expected a string", path: path.slice(), reason: { type: "string" } }; + else + return null; } function _0(object) { ; if (typeof object !== "object" || object === null || Array.isArray(object)) + return { message: "validation failed at " + path.join(".") + ": expected an object", path: path.slice(), reason: { type: "object" } }; { + if ("test" in object) { + path.push("test"); + var error = _string(object["test"]); + path.pop(); + if (error) + return error; + } + else + return { message: "validation failed at " + path.join(".") + ": expected 'test' in object", path: path.slice(), reason: { type: "missing-property", property: "test" } }; + } return null; } return _0(object); }, { async: false })), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], AsyncMethods.prototype, "asyncOverride", null); +__decorate([ + __param(0, index_1.AssertType(object => { var path = ["body"]; function _string(object) { ; if (typeof object !== "string") + return { message: "validation failed at " + path.join(".") + ": expected a string", path: path.slice(), reason: { type: "string" } }; + else + return null; } function _0(object) { ; if (typeof object !== "object" || object === null || Array.isArray(object)) + return { message: "validation failed at " + path.join(".") + ": expected an object", path: path.slice(), reason: { type: "object" } }; { + if ("test" in object) { + path.push("test"); + var error = _string(object["test"]); + path.pop(); + if (error) + return error; + } + else + return { message: "validation failed at " + path.join(".") + ": expected 'test' in object", path: path.slice(), reason: { type: "missing-property", property: "test" } }; + } return null; } return _0(object); })), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Object) +], AsyncMethods.prototype, "promiseOrOtherReturnMethod", null); +AsyncMethods = __decorate([ + index_1.ValidateClass() +], AsyncMethods); +exports.AsyncMethods = AsyncMethods; diff --git a/test/issue-16.ts b/test/issue-16.ts index e75ce96..89dd9c4 100644 --- a/test/issue-16.ts +++ b/test/issue-16.ts @@ -41,7 +41,8 @@ describe('visitor', () => { emitDetailedErrors: 'auto' }, typeMapperStack: [], - previousTypeReference: null + previousTypeReference: null, + canonicalPaths: new Map() }; function visitNodeAndChildren(node: ts.Node) { @@ -68,7 +69,8 @@ describe('visitor', () => { emitDetailedErrors: 'auto' }, typeMapperStack: [], - previousTypeReference: null + previousTypeReference: null, + canonicalPaths: new Map() }; function visitNodeAndChildren(node: ts.Node) { @@ -99,8 +101,9 @@ describe('visitor', () => { emitDetailedErrors: 'auto' }, typeMapperStack: [], - previousTypeReference: null - }; + previousTypeReference: null, + canonicalPaths: new Map() + }; function visitNodeAndChildren(node: ts.Node) { ts.forEachChild(transformNode(node, visitorContext), visitNodeAndChildren); @@ -129,8 +132,9 @@ describe('visitor', () => { emitDetailedErrors: 'auto' }, typeMapperStack: [], - previousTypeReference: null - }; + previousTypeReference: null, + canonicalPaths: new Map() + }; function visitNodeAndChildren(node: ts.Node) { ts.forEachChild(transformNode(node, visitorContext), visitNodeAndChildren); @@ -159,8 +163,9 @@ describe('visitor', () => { emitDetailedErrors: 'auto' }, typeMapperStack: [], - previousTypeReference: null - }; + previousTypeReference: null, + canonicalPaths: new Map() + }; function visitNodeAndChildren(node: ts.Node) { ts.forEachChild(transformNode(node, visitorContext), visitNodeAndChildren); diff --git a/test/issue-27.ts b/test/issue-27.ts index 343cf96..4d8b50e 100644 --- a/test/issue-27.ts +++ b/test/issue-27.ts @@ -39,8 +39,9 @@ describe('visitor', () => { emitDetailedErrors: 'auto' }, typeMapperStack: [], - previousTypeReference: null - }; + previousTypeReference: null, + canonicalPaths: new Map() + }; function visitNodeAndChildren(node: ts.Node) { ts.forEachChild(transformNode(node, visitorContext), visitNodeAndChildren); diff --git a/test/issue-3.ts b/test/issue-3.ts index 9be8944..785f12b 100644 --- a/test/issue-3.ts +++ b/test/issue-3.ts @@ -40,8 +40,9 @@ describe('visitor', () => { emitDetailedErrors: 'auto' }, typeMapperStack: [], - previousTypeReference: null - }; + previousTypeReference: null, + canonicalPaths: new Map() + }; function visitNodeAndChildren(node: ts.Node) { ts.forEachChild(transformNode(node, visitorContext), visitNodeAndChildren); diff --git a/test/issue-31.ts b/test/issue-31.ts index 101577a..c4a69e1 100644 --- a/test/issue-31.ts +++ b/test/issue-31.ts @@ -29,7 +29,8 @@ function createVisitorContext(program: Program, optionsDict: PartialVisitorConte compilerOptions: program.getCompilerOptions(), options: optionsDict, typeMapperStack: [], - previousTypeReference: null + previousTypeReference: null, + canonicalPaths: new Map() }; } diff --git a/test/issue-50.ts b/test/issue-50.ts index b7a3c6a..5d1bd47 100644 --- a/test/issue-50.ts +++ b/test/issue-50.ts @@ -40,8 +40,9 @@ describe('visitor', () => { emitDetailedErrors: 'auto' }, typeMapperStack: [], - previousTypeReference: null - }; + previousTypeReference: null, + canonicalPaths: new Map() + }; function visitNodeAndChildren(node: ts.Node) { ts.forEachChild(transformNode(node, visitorContext), visitNodeAndChildren); @@ -70,8 +71,9 @@ describe('visitor', () => { emitDetailedErrors: 'auto' }, typeMapperStack: [], - previousTypeReference: null - }; + previousTypeReference: null, + canonicalPaths: new Map() + }; function visitNodeAndChildren(node: ts.Node) { ts.forEachChild(transformNode(node, visitorContext), visitNodeAndChildren); diff --git a/test/issue-52.ts b/test/issue-52.ts index 4089153..5765345 100644 --- a/test/issue-52.ts +++ b/test/issue-52.ts @@ -40,8 +40,9 @@ describe('visitor', () => { emitDetailedErrors: 'auto' }, typeMapperStack: [], - previousTypeReference: null - }; + previousTypeReference: null, + canonicalPaths: new Map() + }; function visitNodeAndChildren(node: ts.Node) { ts.forEachChild(transformNode(node, visitorContext), visitNodeAndChildren); @@ -70,8 +71,9 @@ describe('visitor', () => { emitDetailedErrors: 'auto' }, typeMapperStack: [], - previousTypeReference: null - }; + previousTypeReference: null, + canonicalPaths: new Map() + }; function visitNodeAndChildren(node: ts.Node) { ts.forEachChild(transformNode(node, visitorContext), visitNodeAndChildren); @@ -100,8 +102,9 @@ describe('visitor', () => { emitDetailedErrors: 'auto' }, typeMapperStack: [], - previousTypeReference: null - }; + previousTypeReference: null, + canonicalPaths: new Map() + }; function visitNodeAndChildren(node: ts.Node) { ts.forEachChild(transformNode(node, visitorContext), visitNodeAndChildren);