🎯 MapView v2.0 - Global Deployment Ready

 MAJOR FEATURES:
• Auto-zoom intelligence với smart bounds fitting
• Enhanced 3D GPS markers với pulsing effects
• Professional route display với 6-layer rendering
• Status-based parking icons với availability indicators
• Production-ready build optimizations

🗺️ AUTO-ZOOM FEATURES:
• Smart bounds fitting cho GPS + selected parking
• Adaptive padding (50px) cho visual balance
• Max zoom control (level 16) để tránh quá gần
• Dynamic centering khi không có selection

🎨 ENHANCED VISUALS:
• 3D GPS marker với multi-layer pulse effects
• Advanced parking icons với status colors
• Selection highlighting với animation
• Dimming system cho non-selected items

🛣️ ROUTE SYSTEM:
• OpenRouteService API integration
• Multi-layer route rendering (glow, shadow, main, animated)
• Real-time distance & duration calculation
• Visual route info trong popup

📱 PRODUCTION READY:
• SSR safe với dynamic imports
• Build errors resolved
• Global deployment via Vercel
• Optimized performance

🌍 DEPLOYMENT:
• Vercel: https://whatever-ctk2auuxr-phong12hexdockworks-projects.vercel.app
• Bundle size: 22.8 kB optimized
• Global CDN distribution
• HTTPS enabled

💾 VERSION CONTROL:
• MapView-v2.0.tsx backup created
• MAPVIEW_VERSIONS.md documentation
• Full version history tracking
This commit is contained in:
2025-07-20 19:52:16 +07:00
parent 3203463a6a
commit c65cc97a33
64624 changed files with 7199453 additions and 6462 deletions

View File

@@ -0,0 +1,2 @@
import * as ts from 'typescript';
export declare const before: (options?: Record<string, any>, program?: ts.Program) => (ctx: ts.TransformationContext) => ts.Transformer<any>;

View File

@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.before = void 0;
const merge_options_1 = require("./merge-options");
const is_filename_matched_util_1 = require("./utils/is-filename-matched.util");
const controller_class_visitor_1 = require("./visitors/controller-class.visitor");
const model_class_visitor_1 = require("./visitors/model-class.visitor");
const modelClassVisitor = new model_class_visitor_1.ModelClassVisitor();
const controllerClassVisitor = new controller_class_visitor_1.ControllerClassVisitor();
const before = (options, program) => {
options = (0, merge_options_1.mergePluginOptions)(options);
return (ctx) => {
return (sf) => {
if ((0, is_filename_matched_util_1.isFilenameMatched)(options.dtoFileNameSuffix, sf.fileName)) {
return modelClassVisitor.visit(sf, ctx, program, options);
}
if ((0, is_filename_matched_util_1.isFilenameMatched)(options.controllerFileNameSuffix, sf.fileName)) {
return controllerClassVisitor.visit(sf, ctx, program, options);
}
return sf;
};
};
};
exports.before = before;

View File

@@ -0,0 +1,2 @@
export * from './compiler-plugin';
export * from './visitors/readonly.visitor';

View File

@@ -0,0 +1,18 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./compiler-plugin"), exports);
__exportStar(require("./visitors/readonly.visitor"), exports);

View File

@@ -0,0 +1,14 @@
export interface PluginOptions {
dtoFileNameSuffix?: string | string[];
controllerFileNameSuffix?: string | string[];
classValidatorShim?: boolean;
classTransformerShim?: boolean | 'exclusive';
dtoKeyOfComment?: string;
controllerKeyOfComment?: string;
introspectComments?: boolean;
readonly?: boolean;
pathToSource?: string;
debug?: boolean;
parameterProperties?: boolean;
}
export declare const mergePluginOptions: (options?: Record<string, any>) => PluginOptions;

View File

@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mergePluginOptions = void 0;
const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
const defaultOptions = {
dtoFileNameSuffix: ['.dto.ts', '.entity.ts'],
controllerFileNameSuffix: ['.controller.ts'],
classValidatorShim: true,
classTransformerShim: false,
dtoKeyOfComment: 'description',
controllerKeyOfComment: 'description',
introspectComments: false,
readonly: false,
debug: false
};
const mergePluginOptions = (options = {}) => {
if ((0, shared_utils_1.isString)(options.dtoFileNameSuffix)) {
options.dtoFileNameSuffix = [options.dtoFileNameSuffix];
}
if ((0, shared_utils_1.isString)(options.controllerFileNameSuffix)) {
options.controllerFileNameSuffix = [options.controllerFileNameSuffix];
}
return Object.assign(Object.assign({}, defaultOptions), options);
};
exports.mergePluginOptions = mergePluginOptions;

View File

@@ -0,0 +1,7 @@
export declare class MetadataLoader {
private static readonly refreshHooks;
static addRefreshHook(hook: () => void): number;
load(metadata: Record<string, any>): Promise<void>;
private applyMetadata;
private runHooks;
}

View File

@@ -0,0 +1,51 @@
"use strict";
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.MetadataLoader = void 0;
const plugin_constants_1 = require("./plugin-constants");
class MetadataLoader {
static addRefreshHook(hook) {
return MetadataLoader.refreshHooks.push(hook);
}
load(metadata) {
return __awaiter(this, void 0, void 0, function* () {
const pkgMetadata = metadata['@nestjs/swagger'];
if (!pkgMetadata) {
return;
}
const { models, controllers } = pkgMetadata;
if (models) {
yield this.applyMetadata(models);
}
if (controllers) {
yield this.applyMetadata(controllers);
}
this.runHooks();
});
}
applyMetadata(meta) {
return __awaiter(this, void 0, void 0, function* () {
const loadPromises = meta.map(([fileImport, fileMeta]) => __awaiter(this, void 0, void 0, function* () {
const fileRef = yield fileImport;
Object.keys(fileMeta).map((key) => {
const clsRef = fileRef[key];
clsRef[plugin_constants_1.METADATA_FACTORY_NAME] = () => fileMeta[key];
});
}));
yield Promise.all(loadPromises);
});
}
runHooks() {
MetadataLoader.refreshHooks.forEach((hook) => hook());
}
}
exports.MetadataLoader = MetadataLoader;
MetadataLoader.refreshHooks = new Array();

View File

@@ -0,0 +1,3 @@
export declare const OPENAPI_NAMESPACE = "openapi";
export declare const OPENAPI_PACKAGE_NAME = "@nestjs/swagger";
export declare const METADATA_FACTORY_NAME = "_OPENAPI_METADATA_FACTORY";

View File

@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.METADATA_FACTORY_NAME = exports.OPENAPI_PACKAGE_NAME = exports.OPENAPI_NAMESPACE = void 0;
exports.OPENAPI_NAMESPACE = 'openapi';
exports.OPENAPI_PACKAGE_NAME = '@nestjs/swagger';
exports.METADATA_FACTORY_NAME = '_OPENAPI_METADATA_FACTORY';

View File

@@ -0,0 +1,5 @@
import { ConsoleLogger } from '@nestjs/common';
declare class PluginDebugLogger extends ConsoleLogger {
}
export declare const pluginDebugLogger: PluginDebugLogger;
export {};

View File

@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.pluginDebugLogger = void 0;
const common_1 = require("@nestjs/common");
class PluginDebugLogger extends common_1.ConsoleLogger {
}
exports.pluginDebugLogger = new PluginDebugLogger('CLI Plugin');

View File

@@ -0,0 +1,30 @@
import * as ts from 'typescript';
import { Decorator, Node, ObjectFlags, SourceFile, Type, TypeChecker, TypeFlags, TypeFormatFlags, UnionTypeNode } from 'typescript';
import { DocNode, DocComment } from '@microsoft/tsdoc';
export declare function renderDocNode(docNode: DocNode): string;
export declare function isArray(type: Type): boolean;
export declare function getTypeArguments(type: Type): any;
export declare function isBoolean(type: Type): boolean;
export declare function isString(type: Type): boolean;
export declare function isStringLiteral(type: Type): boolean;
export declare function isStringMapping(type: Type): boolean;
export declare function isNumber(type: Type): boolean;
export declare function isBigInt(type: Type): boolean;
export declare function isInterface(type: Type): boolean;
export declare function isEnum(type: Type): boolean;
export declare function isEnumLiteral(type: Type): boolean;
export declare function hasFlag(type: Type, flag: TypeFlags): boolean;
export declare function hasObjectFlag(type: Type, flag: ObjectFlags): boolean;
export declare function getText(type: Type, typeChecker: TypeChecker, enclosingNode?: Node, typeFormatFlags?: TypeFormatFlags): string;
export declare function getDefaultTypeFormatFlags(enclosingNode: Node): number;
export declare function getDocComment(node: Node): DocComment;
export declare function getMainCommentOfNode(node: Node, sourceFile: SourceFile): string;
export declare function parseCommentDocValue(docValue: string, type: ts.Type): string;
export declare function getTsDocTagsOfNode(node: Node, typeChecker: TypeChecker): any;
export declare function getTsDocErrorsOfNode(node: Node): any[];
export declare function getDecoratorArguments(decorator: Decorator): any[] | ts.NodeArray<ts.Expression>;
export declare function getDecoratorName(decorator: Decorator): string;
export declare function findNullableTypeFromUnion(typeNode: UnionTypeNode, typeChecker: TypeChecker): ts.TypeNode;
export declare function createBooleanLiteral(factory: ts.NodeFactory, flag: boolean): ts.BooleanLiteral;
export declare function createPrimitiveLiteral(factory: ts.NodeFactory, item: unknown, typeOfItem?: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"): ts.StringLiteral | ts.NumericLiteral | ts.PrefixUnaryExpression | ts.BooleanLiteral;
export declare function createLiteralFromAnyValue(factory: ts.NodeFactory, item: unknown): any;

View File

@@ -0,0 +1,270 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createLiteralFromAnyValue = exports.createPrimitiveLiteral = exports.createBooleanLiteral = exports.findNullableTypeFromUnion = exports.getDecoratorName = exports.getDecoratorArguments = exports.getTsDocErrorsOfNode = exports.getTsDocTagsOfNode = exports.parseCommentDocValue = exports.getMainCommentOfNode = exports.getDocComment = exports.getDefaultTypeFormatFlags = exports.getText = exports.hasObjectFlag = exports.hasFlag = exports.isEnumLiteral = exports.isEnum = exports.isInterface = exports.isBigInt = exports.isNumber = exports.isStringMapping = exports.isStringLiteral = exports.isString = exports.isBoolean = exports.getTypeArguments = exports.isArray = exports.renderDocNode = void 0;
const typescript_1 = require("typescript");
const plugin_utils_1 = require("./plugin-utils");
const tsdoc_1 = require("@microsoft/tsdoc");
function renderDocNode(docNode) {
let result = '';
if (docNode) {
if (docNode instanceof tsdoc_1.DocExcerpt) {
result += docNode.content.toString();
}
for (const childNode of docNode.getChildNodes()) {
result += renderDocNode(childNode);
}
}
return result;
}
exports.renderDocNode = renderDocNode;
function isArray(type) {
const symbol = type.getSymbol();
if (!symbol) {
return false;
}
return symbol.getName() === 'Array' && getTypeArguments(type).length === 1;
}
exports.isArray = isArray;
function getTypeArguments(type) {
return type.typeArguments || [];
}
exports.getTypeArguments = getTypeArguments;
function isBoolean(type) {
return hasFlag(type, typescript_1.TypeFlags.Boolean);
}
exports.isBoolean = isBoolean;
function isString(type) {
return hasFlag(type, typescript_1.TypeFlags.String);
}
exports.isString = isString;
function isStringLiteral(type) {
return hasFlag(type, typescript_1.TypeFlags.StringLiteral) && !type.isUnion();
}
exports.isStringLiteral = isStringLiteral;
function isStringMapping(type) {
return hasFlag(type, typescript_1.TypeFlags.StringMapping);
}
exports.isStringMapping = isStringMapping;
function isNumber(type) {
return hasFlag(type, typescript_1.TypeFlags.Number);
}
exports.isNumber = isNumber;
function isBigInt(type) {
return hasFlag(type, typescript_1.TypeFlags.BigInt);
}
exports.isBigInt = isBigInt;
function isInterface(type) {
return hasObjectFlag(type, typescript_1.ObjectFlags.Interface);
}
exports.isInterface = isInterface;
function isEnum(type) {
const hasEnumFlag = hasFlag(type, typescript_1.TypeFlags.Enum);
if (hasEnumFlag) {
return true;
}
if (isEnumLiteral(type)) {
return false;
}
const symbol = type.getSymbol();
if (!symbol) {
return false;
}
const valueDeclaration = symbol.valueDeclaration;
if (!valueDeclaration) {
return false;
}
return valueDeclaration.kind === typescript_1.SyntaxKind.EnumDeclaration;
}
exports.isEnum = isEnum;
function isEnumLiteral(type) {
return hasFlag(type, typescript_1.TypeFlags.EnumLiteral) && !type.isUnion();
}
exports.isEnumLiteral = isEnumLiteral;
function hasFlag(type, flag) {
return (type.flags & flag) === flag;
}
exports.hasFlag = hasFlag;
function hasObjectFlag(type, flag) {
return (type.objectFlags & flag) === flag;
}
exports.hasObjectFlag = hasObjectFlag;
function getText(type, typeChecker, enclosingNode, typeFormatFlags) {
if (!typeFormatFlags) {
typeFormatFlags = getDefaultTypeFormatFlags(enclosingNode);
}
const compilerNode = !enclosingNode ? undefined : enclosingNode;
return typeChecker.typeToString(type, compilerNode, typeFormatFlags);
}
exports.getText = getText;
function getDefaultTypeFormatFlags(enclosingNode) {
let formatFlags = typescript_1.TypeFormatFlags.UseTypeOfFunction |
typescript_1.TypeFormatFlags.NoTruncation |
typescript_1.TypeFormatFlags.UseFullyQualifiedType |
typescript_1.TypeFormatFlags.WriteTypeArgumentsOfSignature;
if (enclosingNode && enclosingNode.kind === typescript_1.SyntaxKind.TypeAliasDeclaration)
formatFlags |= typescript_1.TypeFormatFlags.InTypeAlias;
return formatFlags;
}
exports.getDefaultTypeFormatFlags = getDefaultTypeFormatFlags;
function getDocComment(node) {
const tsdocParser = new tsdoc_1.TSDocParser();
const parserContext = tsdocParser.parseString(node.getFullText());
return parserContext.docComment;
}
exports.getDocComment = getDocComment;
function getMainCommentOfNode(node, sourceFile) {
const docComment = getDocComment(node);
return renderDocNode(docComment.summarySection).trim();
}
exports.getMainCommentOfNode = getMainCommentOfNode;
function parseCommentDocValue(docValue, type) {
let value = docValue.replace(/'/g, '"').trim();
if (!type || !isString(type)) {
try {
value = JSON.parse(value);
}
catch (_a) { }
}
else if (isString(type)) {
if (value.split(' ').length !== 1 && !value.startsWith('"')) {
value = null;
}
else {
value = value.replace(/"/g, '');
}
}
return value;
}
exports.parseCommentDocValue = parseCommentDocValue;
function getTsDocTagsOfNode(node, typeChecker) {
const docComment = getDocComment(node);
const tagDefinitions = {
example: {
hasProperties: true,
repeatable: true
}
};
const tagResults = {};
const introspectTsDocTags = (docComment) => {
for (const tag in tagDefinitions) {
const { hasProperties, repeatable } = tagDefinitions[tag];
const blocks = docComment.customBlocks.filter((block) => block.blockTag.tagName === `@${tag}`);
if (blocks.length === 0)
continue;
if (repeatable && !tagResults[tag])
tagResults[tag] = [];
const type = typeChecker.getTypeAtLocation(node);
if (hasProperties) {
blocks.forEach((block) => {
const docValue = renderDocNode(block.content).split('\n')[0];
const value = parseCommentDocValue(docValue, type);
if (value !== null) {
if (repeatable) {
tagResults[tag].push(value);
}
else {
tagResults[tag] = value;
}
}
});
}
else {
tagResults[tag] = true;
}
}
if (docComment.remarksBlock) {
tagResults['remarks'] = renderDocNode(docComment.remarksBlock.content).trim();
}
if (docComment.deprecatedBlock) {
tagResults['deprecated'] = true;
}
};
introspectTsDocTags(docComment);
return tagResults;
}
exports.getTsDocTagsOfNode = getTsDocTagsOfNode;
function getTsDocErrorsOfNode(node) {
const tsdocParser = new tsdoc_1.TSDocParser();
const parserContext = tsdocParser.parseString(node.getFullText());
const docComment = parserContext.docComment;
const tagResults = [];
const errorParsingRegex = /{(\d+)} (.*)/;
const introspectTsDocTags = (docComment) => {
const blocks = docComment.customBlocks.filter((block) => block.blockTag.tagName === '@throws');
blocks.forEach((block) => {
try {
const docValue = renderDocNode(block.content).split('\n')[0].trim();
const match = docValue.match(errorParsingRegex);
tagResults.push({
status: match[1],
description: `"${match[2]}"`
});
}
catch (err) { }
});
};
introspectTsDocTags(docComment);
return tagResults;
}
exports.getTsDocErrorsOfNode = getTsDocErrorsOfNode;
function getDecoratorArguments(decorator) {
const callExpression = decorator.expression;
return (callExpression && callExpression.arguments) || [];
}
exports.getDecoratorArguments = getDecoratorArguments;
function getDecoratorName(decorator) {
const isDecoratorFactory = decorator.expression.kind === typescript_1.SyntaxKind.CallExpression;
if (isDecoratorFactory) {
const callExpression = decorator.expression;
const identifier = callExpression
.expression;
if ((0, plugin_utils_1.isDynamicallyAdded)(identifier)) {
return undefined;
}
return getIdentifierFromName(callExpression.expression).getText();
}
return getIdentifierFromName(decorator.expression).getText();
}
exports.getDecoratorName = getDecoratorName;
function getIdentifierFromName(expression) {
const identifier = getNameFromExpression(expression);
if (expression && expression.kind !== typescript_1.SyntaxKind.Identifier) {
throw new Error();
}
return identifier;
}
function getNameFromExpression(expression) {
if (expression && expression.kind === typescript_1.SyntaxKind.PropertyAccessExpression) {
return expression.name;
}
return expression;
}
function findNullableTypeFromUnion(typeNode, typeChecker) {
return typeNode.types.find((tNode) => hasFlag(typeChecker.getTypeAtLocation(tNode), typescript_1.TypeFlags.Null));
}
exports.findNullableTypeFromUnion = findNullableTypeFromUnion;
function createBooleanLiteral(factory, flag) {
return flag ? factory.createTrue() : factory.createFalse();
}
exports.createBooleanLiteral = createBooleanLiteral;
function createPrimitiveLiteral(factory, item, typeOfItem = typeof item) {
switch (typeOfItem) {
case 'boolean':
return createBooleanLiteral(factory, item);
case 'number': {
if (item < 0) {
return factory.createPrefixUnaryExpression(typescript_1.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(item)));
}
return factory.createNumericLiteral(item);
}
case 'string':
return factory.createStringLiteral(item);
}
}
exports.createPrimitiveLiteral = createPrimitiveLiteral;
function createLiteralFromAnyValue(factory, item) {
return Array.isArray(item)
? factory.createArrayLiteralExpression(item.map((item) => createLiteralFromAnyValue(factory, item)))
: createPrimitiveLiteral(factory, item);
}
exports.createLiteralFromAnyValue = createLiteralFromAnyValue;

View File

@@ -0,0 +1 @@
export declare const isFilenameMatched: (patterns: string[], filename: string) => boolean;

View File

@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isFilenameMatched = void 0;
const isFilenameMatched = (patterns, filename) => patterns.some((path) => filename.includes(path));
exports.isFilenameMatched = isFilenameMatched;

View File

@@ -0,0 +1,29 @@
import * as ts from 'typescript';
import { PluginOptions } from '../merge-options';
export declare function getDecoratorOrUndefinedByNames(names: string[], decorators: readonly ts.Decorator[], factory: ts.NodeFactory): ts.Decorator | undefined;
export declare function getTypeReferenceAsString(type: ts.Type, typeChecker: ts.TypeChecker, arrayDepth?: number): {
typeName: string;
isArray?: boolean;
arrayDepth?: number;
};
export declare function isPromiseOrObservable(type: string): boolean;
export declare function hasPropertyKey(key: string, properties: ts.NodeArray<ts.PropertyAssignment>): boolean;
export declare function replaceImportPath(typeReference: string, fileName: string, options: PluginOptions): {
typeReference: string;
typeName: string;
importPath: string;
} | {
typeReference: string;
importPath: string;
typeName?: undefined;
};
export declare function insertAt(string: string, index: number, substring: string): string;
export declare function isDynamicallyAdded(identifier: ts.Node): boolean;
export declare function isAutoGeneratedEnumUnion(type: ts.Type, typeChecker: ts.TypeChecker): ts.Type;
export declare function isAutoGeneratedTypeUnion(type: ts.Type): boolean;
export declare function extractTypeArgumentIfArray(type: ts.Type): {
type: ts.Type;
isArray: boolean;
};
export declare function convertPath(windowsPath: string): string;
export declare function canReferenceNode(node: ts.Node, options: PluginOptions): boolean;

View File

@@ -0,0 +1,279 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.canReferenceNode = exports.convertPath = exports.extractTypeArgumentIfArray = exports.isAutoGeneratedTypeUnion = exports.isAutoGeneratedEnumUnion = exports.isDynamicallyAdded = exports.insertAt = exports.replaceImportPath = exports.hasPropertyKey = exports.isPromiseOrObservable = exports.getTypeReferenceAsString = exports.getDecoratorOrUndefinedByNames = void 0;
const lodash_1 = require("lodash");
const path_1 = require("path");
const ts = require("typescript");
const ast_utils_1 = require("./ast-utils");
function getDecoratorOrUndefinedByNames(names, decorators, factory) {
return (decorators || factory.createNodeArray()).find((item) => {
try {
const decoratorName = (0, ast_utils_1.getDecoratorName)(item);
return names.includes(decoratorName);
}
catch (_a) {
return false;
}
});
}
exports.getDecoratorOrUndefinedByNames = getDecoratorOrUndefinedByNames;
function getTypeReferenceAsString(type, typeChecker, arrayDepth = 0) {
if ((0, ast_utils_1.isArray)(type)) {
const arrayType = (0, ast_utils_1.getTypeArguments)(type)[0];
const { typeName, arrayDepth: depth } = getTypeReferenceAsString(arrayType, typeChecker, arrayDepth + 1);
if (!typeName) {
return { typeName: undefined };
}
return {
typeName: `${typeName}`,
isArray: true,
arrayDepth: depth
};
}
if ((0, ast_utils_1.isBoolean)(type)) {
return { typeName: Boolean.name, arrayDepth };
}
if ((0, ast_utils_1.isNumber)(type)) {
return { typeName: Number.name, arrayDepth };
}
if ((0, ast_utils_1.isBigInt)(type)) {
return { typeName: BigInt.name, arrayDepth };
}
if ((0, ast_utils_1.isString)(type) || (0, ast_utils_1.isStringLiteral)(type) || (0, ast_utils_1.isStringMapping)(type)) {
return { typeName: String.name, arrayDepth };
}
if (isPromiseOrObservable((0, ast_utils_1.getText)(type, typeChecker))) {
const typeArguments = (0, ast_utils_1.getTypeArguments)(type);
const elementType = getTypeReferenceAsString((0, lodash_1.head)(typeArguments), typeChecker, arrayDepth);
return elementType;
}
if (type.isClass()) {
return { typeName: (0, ast_utils_1.getText)(type, typeChecker), arrayDepth };
}
try {
const text = (0, ast_utils_1.getText)(type, typeChecker);
if (text === Date.name) {
return { typeName: text, arrayDepth };
}
if (isOptionalBoolean(text)) {
return { typeName: Boolean.name, arrayDepth };
}
if (isAutoGeneratedTypeUnion(type) ||
isAutoGeneratedEnumUnion(type, typeChecker)) {
const types = type.types;
return getTypeReferenceAsString(types[types.length - 1], typeChecker, arrayDepth);
}
if (text === 'any' ||
text === 'unknown' ||
text === 'object' ||
(0, ast_utils_1.isInterface)(type) ||
(type.isUnionOrIntersection() && !(0, ast_utils_1.isEnum)(type))) {
return { typeName: 'Object', arrayDepth };
}
if ((0, ast_utils_1.isEnum)(type)) {
return { typeName: undefined, arrayDepth };
}
if (type.aliasSymbol) {
return { typeName: 'Object', arrayDepth };
}
if (typeChecker.getApparentType(type).getSymbol().getEscapedName() === 'String') {
return { typeName: String.name, arrayDepth };
}
return { typeName: undefined };
}
catch (_a) {
return { typeName: undefined };
}
}
exports.getTypeReferenceAsString = getTypeReferenceAsString;
function isPromiseOrObservable(type) {
return type.includes('Promise<') || type.includes('Observable<');
}
exports.isPromiseOrObservable = isPromiseOrObservable;
function hasPropertyKey(key, properties) {
return properties
.filter((item) => !isDynamicallyAdded(item))
.some((item) => item.name.getText() === key);
}
exports.hasPropertyKey = hasPropertyKey;
function replaceImportPath(typeReference, fileName, options) {
if (!typeReference.includes('import')) {
return { typeReference, importPath: null };
}
let importPath = /\(\"([^)]).+(\")/.exec(typeReference)[0];
if (!importPath) {
return { typeReference: undefined, importPath: null };
}
importPath = convertPath(importPath);
importPath = importPath.slice(2, importPath.length - 1);
try {
if ((0, path_1.isAbsolute)(importPath)) {
throw {};
}
require.resolve(importPath);
typeReference = typeReference.replace('import', 'require');
return {
typeReference,
importPath: null
};
}
catch (_error) {
const from = (options === null || options === void 0 ? void 0 : options.readonly)
? convertPath(options.pathToSource)
: path_1.posix.dirname(convertPath(fileName));
let relativePath = path_1.posix.relative(from, importPath);
relativePath = relativePath[0] !== '.' ? './' + relativePath : relativePath;
const nodeModulesText = 'node_modules';
const nodeModulePos = relativePath.indexOf(nodeModulesText);
if (nodeModulePos >= 0) {
relativePath = relativePath.slice(nodeModulePos + nodeModulesText.length + 1);
const typesText = '@types';
const typesPos = relativePath.indexOf(typesText);
if (typesPos >= 0) {
relativePath = relativePath.slice(typesPos + typesText.length + 1);
}
const indexText = '/index';
const indexPos = relativePath.indexOf(indexText);
if (indexPos >= 0) {
relativePath = relativePath.slice(0, indexPos);
}
}
typeReference = typeReference.replace(importPath, relativePath);
if (options.readonly) {
const { typeName, typeImportStatement } = convertToAsyncImport(typeReference);
return {
typeReference: typeImportStatement,
typeName,
importPath: relativePath
};
}
return {
typeReference: typeReference.replace('import', 'require'),
importPath: relativePath
};
}
}
exports.replaceImportPath = replaceImportPath;
function convertToAsyncImport(typeReference) {
const regexp = /import\(.+\).([^\]]+)(\])?/;
const match = regexp.exec(typeReference);
if ((match === null || match === void 0 ? void 0 : match.length) >= 2) {
const importPos = typeReference.indexOf(match[0]);
typeReference = typeReference.replace(`.${match[1]}`, '');
return {
typeImportStatement: insertAt(typeReference, importPos, 'await '),
typeName: match[1]
};
}
return { typeImportStatement: typeReference };
}
function insertAt(string, index, substring) {
return string.slice(0, index) + substring + string.slice(index);
}
exports.insertAt = insertAt;
function isDynamicallyAdded(identifier) {
return identifier && !identifier.parent && identifier.pos === -1;
}
exports.isDynamicallyAdded = isDynamicallyAdded;
function isAutoGeneratedEnumUnion(type, typeChecker) {
if (type.isUnionOrIntersection() && !(0, ast_utils_1.isEnum)(type)) {
if (!type.types) {
return undefined;
}
const undefinedTypeIndex = type.types.findIndex((type) => type.intrinsicName === 'undefined' || type.intrinsicName === 'null');
if (undefinedTypeIndex < 0) {
return undefined;
}
let parentType = undefined;
const isParentSymbolEqual = type.types.every((item, index) => {
if (index === undefinedTypeIndex) {
return true;
}
if (!item.symbol) {
return false;
}
if (!item.symbol.parent ||
item.symbol.flags !== ts.SymbolFlags.EnumMember) {
return false;
}
const symbolType = typeChecker.getDeclaredTypeOfSymbol(item.symbol.parent);
if (symbolType === parentType || !parentType) {
parentType = symbolType;
return true;
}
return false;
});
if (isParentSymbolEqual) {
return parentType;
}
}
return undefined;
}
exports.isAutoGeneratedEnumUnion = isAutoGeneratedEnumUnion;
function isAutoGeneratedTypeUnion(type) {
if (type.isUnionOrIntersection() && !(0, ast_utils_1.isEnum)(type)) {
if (!type.types) {
return false;
}
const undefinedTypeIndex = type.types.findIndex((type) => type.intrinsicName === 'undefined');
if (type.types.length === 2 && undefinedTypeIndex >= 0) {
return true;
}
}
return false;
}
exports.isAutoGeneratedTypeUnion = isAutoGeneratedTypeUnion;
function extractTypeArgumentIfArray(type) {
if ((0, ast_utils_1.isArray)(type)) {
type = (0, ast_utils_1.getTypeArguments)(type)[0];
if (!type) {
return undefined;
}
return {
type,
isArray: true
};
}
return {
type,
isArray: false
};
}
exports.extractTypeArgumentIfArray = extractTypeArgumentIfArray;
function isOptionalBoolean(text) {
return typeof text === 'string' && text === 'boolean | undefined';
}
function convertPath(windowsPath) {
return windowsPath
.replace(/^\\\\\?\\/, '')
.replace(/\\/g, '/')
.replace(/\/\/+/g, '/');
}
exports.convertPath = convertPath;
function canReferenceNode(node, options) {
var _a;
if (!options.readonly) {
return true;
}
if (ts.isCallExpression(node) || ts.isIdentifier(node)) {
return false;
}
if (ts.isNewExpression(node)) {
if (((_a = node.expression) === null || _a === void 0 ? void 0 : _a.escapedText) === 'Date') {
return true;
}
return false;
}
if (node.kind === ts.SyntaxKind.FalseKeyword ||
node.kind === ts.SyntaxKind.TrueKeyword ||
node.kind === ts.SyntaxKind.NullKeyword) {
return true;
}
if (ts.isNumericLiteral(node) ||
ts.isPrefixUnaryExpression(node) ||
ts.isStringLiteral(node)) {
return true;
}
return false;
}
exports.canReferenceNode = canReferenceNode;

View File

@@ -0,0 +1,7 @@
import * as ts from 'typescript';
import { PluginOptions } from '../merge-options';
export declare function typeReferenceToIdentifier(typeReferenceDescriptor: {
typeName: string;
isArray?: boolean;
arrayDepth?: number;
}, hostFilename: string, options: PluginOptions, factory: ts.NodeFactory, type: ts.Type, typeImports: Record<string, string>): ts.Identifier;

View File

@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.typeReferenceToIdentifier = void 0;
const plugin_debug_logger_1 = require("../plugin-debug-logger");
const plugin_utils_1 = require("./plugin-utils");
function typeReferenceToIdentifier(typeReferenceDescriptor, hostFilename, options, factory, type, typeImports) {
if (options.readonly) {
assertReferenceableType(type, typeReferenceDescriptor.typeName, hostFilename, options);
}
const { typeReference, importPath, typeName } = (0, plugin_utils_1.replaceImportPath)(typeReferenceDescriptor.typeName, hostFilename, options);
let identifier;
if (options.readonly && (typeReference === null || typeReference === void 0 ? void 0 : typeReference.includes('import'))) {
if (!typeImports[importPath]) {
typeImports[importPath] = typeReference;
}
let ref = `t["${importPath}"].${typeName}`;
if (typeReferenceDescriptor.isArray) {
ref = wrapTypeInArray(ref, typeReferenceDescriptor.arrayDepth);
}
identifier = factory.createIdentifier(ref);
}
else {
let ref = typeReference;
if (typeReferenceDescriptor.isArray) {
ref = wrapTypeInArray(ref, typeReferenceDescriptor.arrayDepth);
}
identifier = factory.createIdentifier(ref);
}
return identifier;
}
exports.typeReferenceToIdentifier = typeReferenceToIdentifier;
function wrapTypeInArray(typeRef, arrayDepth) {
for (let i = 0; i < arrayDepth; i++) {
typeRef = `[${typeRef}]`;
}
return typeRef;
}
function assertReferenceableType(type, parsedTypeName, hostFilename, options) {
if (!type.symbol) {
return true;
}
if (!type.symbol.isReferenced) {
return true;
}
if (parsedTypeName.includes('import')) {
return true;
}
const errorMessage = `Type "${parsedTypeName}" is not referenceable ("${hostFilename}"). To fix this, make sure to export this type.`;
if (options.debug) {
plugin_debug_logger_1.pluginDebugLogger.debug(errorMessage);
}
throw new Error(errorMessage);
}

View File

@@ -0,0 +1,4 @@
import * as ts from 'typescript';
export declare class AbstractFileVisitor {
updateImports(sourceFile: ts.SourceFile, factory: ts.NodeFactory | undefined, program: ts.Program): ts.SourceFile;
}

View File

@@ -0,0 +1,31 @@
"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AbstractFileVisitor = void 0;
const ts = require("typescript");
const plugin_constants_1 = require("../plugin-constants");
const [major, minor] = (_a = ts.versionMajorMinor) === null || _a === void 0 ? void 0 : _a.split('.').map((x) => +x);
class AbstractFileVisitor {
updateImports(sourceFile, factory, program) {
if (major <= 4 && minor < 8) {
throw new Error('Nest CLI plugin does not support TypeScript < v4.8');
}
const importEqualsDeclaration = factory.createImportEqualsDeclaration(undefined, false, factory.createIdentifier(plugin_constants_1.OPENAPI_NAMESPACE), factory.createExternalModuleReference(factory.createStringLiteral(plugin_constants_1.OPENAPI_PACKAGE_NAME)));
const compilerOptions = program.getCompilerOptions();
if (compilerOptions.module >= ts.ModuleKind.ES2015 &&
compilerOptions.module <= ts.ModuleKind.ESNext) {
const importAsDeclaration = factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamespaceImport(factory.createIdentifier(plugin_constants_1.OPENAPI_NAMESPACE))), factory.createStringLiteral(plugin_constants_1.OPENAPI_PACKAGE_NAME), undefined);
return factory.updateSourceFile(sourceFile, [
importAsDeclaration,
...sourceFile.statements
]);
}
else {
return factory.updateSourceFile(sourceFile, [
importEqualsDeclaration,
...sourceFile.statements
]);
}
}
}
exports.AbstractFileVisitor = AbstractFileVisitor;

View File

@@ -0,0 +1,23 @@
import * as ts from 'typescript';
import { PluginOptions } from '../merge-options';
import { AbstractFileVisitor } from './abstract.visitor';
type ClassMetadata = Record<string, ts.ObjectLiteralExpression>;
export declare class ControllerClassVisitor extends AbstractFileVisitor {
private readonly _collectedMetadata;
private readonly _typeImports;
get typeImports(): Record<string, string>;
get collectedMetadata(): Array<[
ts.CallExpression,
Record<string, ClassMetadata>
]>;
visit(sourceFile: ts.SourceFile, ctx: ts.TransformationContext, program: ts.Program, options: PluginOptions): ts.Node;
addDecoratorToNode(factory: ts.NodeFactory, compilerNode: ts.MethodDeclaration, typeChecker: ts.TypeChecker, options: PluginOptions, sourceFile: ts.SourceFile, metadata: ClassMetadata): ts.MethodDeclaration;
createApiOperationDecorator(factory: ts.NodeFactory, node: ts.MethodDeclaration, decorators: readonly ts.Decorator[], options: PluginOptions, sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker, metadata: ClassMetadata): ts.Decorator[];
createApiResponseDecorator(factory: ts.NodeFactory, node: ts.MethodDeclaration, decorators: readonly ts.Decorator[], options: PluginOptions, sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker, metadata: ClassMetadata): ts.Decorator[];
createDecoratorObjectLiteralExpr(factory: ts.NodeFactory, node: ts.MethodDeclaration, typeChecker: ts.TypeChecker, existingProperties: ts.NodeArray<ts.PropertyAssignment>, hostFilename: string, metadata: ClassMetadata, options: PluginOptions): ts.ObjectLiteralExpression;
createTypePropertyAssignment(factory: ts.NodeFactory, node: ts.MethodDeclaration, typeChecker: ts.TypeChecker, existingProperties: ts.NodeArray<ts.PropertyAssignment>, hostFilename: string, options: PluginOptions): ts.PropertyAssignment;
createStatusPropertyAssignment(factory: ts.NodeFactory, node: ts.MethodDeclaration, existingProperties: ts.NodeArray<ts.PropertyAssignment>): ts.PropertyAssignment;
getStatusCodeIdentifier(factory: ts.NodeFactory, node: ts.MethodDeclaration): any;
private normalizeImportPath;
}
export {};

View File

@@ -0,0 +1,266 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ControllerClassVisitor = void 0;
const lodash_1 = require("lodash");
const path_1 = require("path");
const ts = require("typescript");
const decorators_1 = require("../../decorators");
const plugin_constants_1 = require("../plugin-constants");
const ast_utils_1 = require("../utils/ast-utils");
const plugin_utils_1 = require("../utils/plugin-utils");
const type_reference_to_identifier_util_1 = require("../utils/type-reference-to-identifier.util");
const abstract_visitor_1 = require("./abstract.visitor");
class ControllerClassVisitor extends abstract_visitor_1.AbstractFileVisitor {
constructor() {
super(...arguments);
this._collectedMetadata = {};
this._typeImports = {};
}
get typeImports() {
return this._typeImports;
}
get collectedMetadata() {
const metadataWithImports = [];
Object.keys(this._collectedMetadata).forEach((filePath) => {
const metadata = this._collectedMetadata[filePath];
const path = filePath.replace(/\.[jt]s$/, '');
const importExpr = ts.factory.createCallExpression(ts.factory.createToken(ts.SyntaxKind.ImportKeyword), undefined, [ts.factory.createStringLiteral(path)]);
metadataWithImports.push([importExpr, metadata]);
});
return metadataWithImports;
}
visit(sourceFile, ctx, program, options) {
const typeChecker = program.getTypeChecker();
if (!options.readonly) {
sourceFile = this.updateImports(sourceFile, ctx.factory, program);
}
const visitNode = (node) => {
var _a;
if (ts.isMethodDeclaration(node)) {
try {
const metadata = {};
const updatedNode = this.addDecoratorToNode(ctx.factory, node, typeChecker, options, sourceFile, metadata);
if (!options.readonly) {
return updatedNode;
}
else {
const filePath = this.normalizeImportPath(options.pathToSource, sourceFile.fileName);
if (!this._collectedMetadata[filePath]) {
this._collectedMetadata[filePath] = {};
}
const parent = node.parent;
const clsName = (_a = parent.name) === null || _a === void 0 ? void 0 : _a.getText();
if (clsName) {
if (!this._collectedMetadata[filePath][clsName]) {
this._collectedMetadata[filePath][clsName] = {};
}
Object.assign(this._collectedMetadata[filePath][clsName], metadata);
}
}
}
catch (_b) {
if (!options.readonly) {
return node;
}
}
}
if (options.readonly) {
ts.forEachChild(node, visitNode);
}
else {
return ts.visitEachChild(node, visitNode, ctx);
}
};
return ts.visitNode(sourceFile, visitNode);
}
addDecoratorToNode(factory, compilerNode, typeChecker, options, sourceFile, metadata) {
var _a;
const hostFilename = sourceFile.fileName;
const decorators = ts.canHaveDecorators(compilerNode) && ts.getDecorators(compilerNode);
if (!decorators) {
return compilerNode;
}
const apiOperationDecoratorsArray = this.createApiOperationDecorator(factory, compilerNode, decorators, options, sourceFile, typeChecker, metadata);
const apiResponseDecoratorsArray = this.createApiResponseDecorator(factory, compilerNode, decorators, options, sourceFile, typeChecker, metadata);
const removeExistingApiOperationDecorator = apiOperationDecoratorsArray.length > 0;
const existingDecorators = removeExistingApiOperationDecorator
? decorators.filter((item) => (0, ast_utils_1.getDecoratorName)(item) !== decorators_1.ApiOperation.name)
: decorators;
const modifiers = (_a = ts.getModifiers(compilerNode)) !== null && _a !== void 0 ? _a : [];
const objectLiteralExpr = this.createDecoratorObjectLiteralExpr(factory, compilerNode, typeChecker, factory.createNodeArray(), hostFilename, metadata, options);
const updatedDecorators = [
...apiOperationDecoratorsArray,
...apiResponseDecoratorsArray,
...existingDecorators,
factory.createDecorator(factory.createCallExpression(factory.createIdentifier(`${plugin_constants_1.OPENAPI_NAMESPACE}.${decorators_1.ApiResponse.name}`), undefined, [factory.createObjectLiteralExpression(objectLiteralExpr.properties)]))
];
if (!options.readonly) {
return factory.updateMethodDeclaration(compilerNode, [...updatedDecorators, ...modifiers], compilerNode.asteriskToken, compilerNode.name, compilerNode.questionToken, compilerNode.typeParameters, compilerNode.parameters, compilerNode.type, compilerNode.body);
}
else {
return compilerNode;
}
}
createApiOperationDecorator(factory, node, decorators, options, sourceFile, typeChecker, metadata) {
if (!options.introspectComments) {
return [];
}
const keyToGenerate = options.controllerKeyOfComment;
const apiOperationDecorator = (0, plugin_utils_1.getDecoratorOrUndefinedByNames)([decorators_1.ApiOperation.name], decorators, factory);
let apiOperationExistingProps = undefined;
if (apiOperationDecorator && !options.readonly) {
const apiOperationExpr = (0, lodash_1.head)((0, ast_utils_1.getDecoratorArguments)(apiOperationDecorator));
if (apiOperationExpr) {
apiOperationExistingProps =
apiOperationExpr.properties;
}
}
const extractedComments = (0, ast_utils_1.getMainCommentOfNode)(node, sourceFile);
if (!extractedComments) {
return [];
}
const tags = (0, ast_utils_1.getTsDocTagsOfNode)(node, typeChecker);
const properties = [
factory.createPropertyAssignment(keyToGenerate, factory.createStringLiteral(extractedComments)),
...(apiOperationExistingProps !== null && apiOperationExistingProps !== void 0 ? apiOperationExistingProps : factory.createNodeArray())
];
const hasRemarksKey = (0, plugin_utils_1.hasPropertyKey)('description', factory.createNodeArray(apiOperationExistingProps));
if (!hasRemarksKey && tags.remarks) {
const remarksPropertyAssignment = factory.createPropertyAssignment('description', (0, ast_utils_1.createLiteralFromAnyValue)(factory, tags.remarks));
properties.push(remarksPropertyAssignment);
}
const hasDeprecatedKey = (0, plugin_utils_1.hasPropertyKey)('deprecated', factory.createNodeArray(apiOperationExistingProps));
if (!hasDeprecatedKey && tags.deprecated) {
const deprecatedPropertyAssignment = factory.createPropertyAssignment('deprecated', (0, ast_utils_1.createLiteralFromAnyValue)(factory, tags.deprecated));
properties.push(deprecatedPropertyAssignment);
}
const objectLiteralExpr = factory.createObjectLiteralExpression((0, lodash_1.compact)(properties));
const apiOperationDecoratorArguments = factory.createNodeArray([objectLiteralExpr]);
const methodKey = node.name.getText();
if (metadata[methodKey]) {
const existingObjectLiteralExpr = metadata[methodKey];
const existingProperties = existingObjectLiteralExpr.properties;
const updatedProperties = factory.createNodeArray([
...existingProperties,
...(0, lodash_1.compact)(properties)
]);
const updatedObjectLiteralExpr = factory.createObjectLiteralExpression(updatedProperties);
metadata[methodKey] = updatedObjectLiteralExpr;
}
else {
metadata[methodKey] = objectLiteralExpr;
}
if (apiOperationDecorator) {
const expr = apiOperationDecorator.expression;
const updatedCallExpr = factory.updateCallExpression(expr, expr.expression, undefined, apiOperationDecoratorArguments);
return [factory.updateDecorator(apiOperationDecorator, updatedCallExpr)];
}
else {
return [
factory.createDecorator(factory.createCallExpression(factory.createIdentifier(`${plugin_constants_1.OPENAPI_NAMESPACE}.${decorators_1.ApiOperation.name}`), undefined, apiOperationDecoratorArguments))
];
}
}
createApiResponseDecorator(factory, node, decorators, options, sourceFile, typeChecker, metadata) {
if (!options.introspectComments) {
return [];
}
const apiResponseDecorator = (0, plugin_utils_1.getDecoratorOrUndefinedByNames)([decorators_1.ApiResponse.name], decorators, factory);
let apiResponseExistingProps = undefined;
if (apiResponseDecorator && !options.readonly) {
const apiResponseExpr = (0, lodash_1.head)((0, ast_utils_1.getDecoratorArguments)(apiResponseDecorator));
if (apiResponseExpr) {
apiResponseExistingProps =
apiResponseExpr.properties;
}
}
const tags = (0, ast_utils_1.getTsDocErrorsOfNode)(node);
if (!tags.length) {
return [];
}
return tags.map((tag) => {
const properties = [
...(apiResponseExistingProps !== null && apiResponseExistingProps !== void 0 ? apiResponseExistingProps : factory.createNodeArray())
];
properties.push(factory.createPropertyAssignment('status', factory.createNumericLiteral(tag.status)));
properties.push(factory.createPropertyAssignment('description', factory.createNumericLiteral(tag.description)));
const objectLiteralExpr = factory.createObjectLiteralExpression((0, lodash_1.compact)(properties));
const methodKey = node.name.getText();
metadata[methodKey] = objectLiteralExpr;
const apiResponseDecoratorArguments = factory.createNodeArray([objectLiteralExpr]);
return factory.createDecorator(factory.createCallExpression(factory.createIdentifier(`${plugin_constants_1.OPENAPI_NAMESPACE}.${decorators_1.ApiResponse.name}`), undefined, apiResponseDecoratorArguments));
});
}
createDecoratorObjectLiteralExpr(factory, node, typeChecker, existingProperties = factory.createNodeArray(), hostFilename, metadata, options) {
let properties = [];
if (!options.readonly) {
properties = properties.concat(existingProperties, this.createStatusPropertyAssignment(factory, node, existingProperties));
}
properties = properties.concat([
this.createTypePropertyAssignment(factory, node, typeChecker, existingProperties, hostFilename, options)
]);
const objectLiteralExpr = factory.createObjectLiteralExpression((0, lodash_1.compact)(properties));
const methodKey = node.name.getText();
const existingExprOrUndefined = metadata[methodKey];
if (existingExprOrUndefined) {
const existingProperties = existingExprOrUndefined.properties;
const updatedProperties = factory.createNodeArray([
...existingProperties,
...(0, lodash_1.compact)(properties)
]);
const updatedObjectLiteralExpr = factory.createObjectLiteralExpression(updatedProperties);
metadata[methodKey] = updatedObjectLiteralExpr;
}
else {
metadata[methodKey] = objectLiteralExpr;
}
return objectLiteralExpr;
}
createTypePropertyAssignment(factory, node, typeChecker, existingProperties, hostFilename, options) {
if ((0, plugin_utils_1.hasPropertyKey)('type', existingProperties)) {
return undefined;
}
const signature = typeChecker.getSignatureFromDeclaration(node);
const type = typeChecker.getReturnTypeOfSignature(signature);
if (!type) {
return undefined;
}
const typeReferenceDescriptor = (0, plugin_utils_1.getTypeReferenceAsString)(type, typeChecker);
if (!typeReferenceDescriptor.typeName) {
return undefined;
}
if (typeReferenceDescriptor.typeName.includes('node_modules')) {
return undefined;
}
const identifier = (0, type_reference_to_identifier_util_1.typeReferenceToIdentifier)(typeReferenceDescriptor, hostFilename, options, factory, type, this._typeImports);
return factory.createPropertyAssignment('type', identifier);
}
createStatusPropertyAssignment(factory, node, existingProperties) {
if ((0, plugin_utils_1.hasPropertyKey)('status', existingProperties)) {
return undefined;
}
const statusNode = this.getStatusCodeIdentifier(factory, node);
return factory.createPropertyAssignment('status', statusNode);
}
getStatusCodeIdentifier(factory, node) {
const decorators = ts.canHaveDecorators(node) && ts.getDecorators(node);
const httpCodeDecorator = (0, plugin_utils_1.getDecoratorOrUndefinedByNames)(['HttpCode'], decorators, factory);
if (httpCodeDecorator) {
const argument = (0, lodash_1.head)((0, ast_utils_1.getDecoratorArguments)(httpCodeDecorator));
if (argument) {
return argument;
}
}
const postDecorator = (0, plugin_utils_1.getDecoratorOrUndefinedByNames)(['Post'], decorators, factory);
if (postDecorator) {
return factory.createIdentifier('201');
}
return factory.createIdentifier('200');
}
normalizeImportPath(pathToSource, path) {
let relativePath = path_1.posix.relative((0, plugin_utils_1.convertPath)(pathToSource), (0, plugin_utils_1.convertPath)(path));
relativePath = relativePath[0] !== '.' ? './' + relativePath : relativePath;
return relativePath;
}
}
exports.ControllerClassVisitor = ControllerClassVisitor;

View File

@@ -0,0 +1,37 @@
import * as ts from 'typescript';
import { PropertyAssignment } from 'typescript';
import { PluginOptions } from '../merge-options';
import { AbstractFileVisitor } from './abstract.visitor';
type ClassMetadata = Record<string, ts.ObjectLiteralExpression>;
export declare class ModelClassVisitor extends AbstractFileVisitor {
private readonly _typeImports;
private readonly _collectedMetadata;
get typeImports(): Record<string, string>;
get collectedMetadata(): Array<[
ts.CallExpression,
Record<string, ClassMetadata>
]>;
visit(sourceFile: ts.SourceFile, ctx: ts.TransformationContext, program: ts.Program, options: PluginOptions): ts.Node;
visitPropertyNodeDeclaration(node: ts.PropertyDeclaration, ctx: ts.TransformationContext, typeChecker: ts.TypeChecker, options: PluginOptions, sourceFile: ts.SourceFile, metadata: ClassMetadata): ts.PropertyDeclaration;
visitConstructorDeclarationNode(constructorNode: ts.ConstructorDeclaration, typeChecker: ts.TypeChecker, options: PluginOptions, sourceFile: ts.SourceFile, metadata: ClassMetadata): void;
addMetadataFactory(factory: ts.NodeFactory, node: ts.ClassDeclaration, classMetadata: ClassMetadata, sourceFile: ts.SourceFile, options: PluginOptions): ts.ClassDeclaration;
inspectPropertyDeclaration(factory: ts.NodeFactory, compilerNode: ts.PropertyDeclaration, typeChecker: ts.TypeChecker, options: PluginOptions, hostFilename: string, sourceFile: ts.SourceFile, metadata: ClassMetadata): void;
createDecoratorObjectLiteralExpr(factory: ts.NodeFactory, node: ts.PropertyDeclaration | ts.PropertySignature | ts.ParameterDeclaration, typeChecker: ts.TypeChecker, existingProperties?: ts.NodeArray<ts.PropertyAssignment>, options?: PluginOptions, hostFilename?: string, sourceFile?: ts.SourceFile): ts.ObjectLiteralExpression;
private createTypePropertyAssignments;
createInitializerForTypeLiteralNode(node: ts.TypeLiteralNode, factory: ts.NodeFactory, typeChecker: ts.TypeChecker, existingProperties: ts.NodeArray<ts.PropertyAssignment>, hostFilename: string, options: PluginOptions): ts.ArrowFunction;
isNullableUnion(node: ts.UnionTypeNode): {
nullableType: ts.TypeNode;
isNullable: boolean;
};
createEnumPropertyAssignment(factory: ts.NodeFactory, node: ts.PropertyDeclaration | ts.PropertySignature | ts.ParameterDeclaration, typeChecker: ts.TypeChecker, existingProperties: ts.NodeArray<ts.PropertyAssignment>, hostFilename: string, options: PluginOptions): ts.PropertyAssignment | ts.PropertyAssignment[];
createDefaultPropertyAssignment(factory: ts.NodeFactory, node: ts.PropertyDeclaration | ts.PropertySignature | ts.ParameterDeclaration, existingProperties: ts.NodeArray<ts.PropertyAssignment>, options: PluginOptions): ts.PropertyAssignment;
createValidationPropertyAssignments(factory: ts.NodeFactory, node: ts.PropertyDeclaration | ts.PropertySignature, options: PluginOptions): ts.PropertyAssignment[];
addPropertyByValidationDecorator(factory: ts.NodeFactory, decoratorName: string, propertyKey: string, decorators: readonly ts.Decorator[], assignments: ts.PropertyAssignment[], options: PluginOptions): void;
addPropertiesByValidationDecorator(factory: ts.NodeFactory, decoratorName: string, decorators: readonly ts.Decorator[], assignments: ts.PropertyAssignment[], addPropertyAssignments: (decoratorRef: ts.Decorator) => PropertyAssignment[]): void;
addClassMetadata(node: ts.PropertyDeclaration, objectLiteral: ts.ObjectLiteralExpression, sourceFile: ts.SourceFile, metadata: ClassMetadata): void;
createDescriptionAndTsDocTagPropertyAssignments(factory: ts.NodeFactory, node: ts.PropertyDeclaration | ts.PropertySignature | ts.ParameterDeclaration, typeChecker: ts.TypeChecker, existingProperties?: ts.NodeArray<ts.PropertyAssignment>, options?: PluginOptions, sourceFile?: ts.SourceFile): ts.PropertyAssignment[];
private normalizeImportPath;
private clonePrimitiveLiteral;
private getInitializerPrimitiveTypeName;
}
export {};

View File

@@ -0,0 +1,426 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModelClassVisitor = void 0;
const lodash_1 = require("lodash");
const path_1 = require("path");
const ts = require("typescript");
const typescript_1 = require("typescript");
const decorators_1 = require("../../decorators");
const plugin_constants_1 = require("../plugin-constants");
const plugin_debug_logger_1 = require("../plugin-debug-logger");
const ast_utils_1 = require("../utils/ast-utils");
const plugin_utils_1 = require("../utils/plugin-utils");
const type_reference_to_identifier_util_1 = require("../utils/type-reference-to-identifier.util");
const abstract_visitor_1 = require("./abstract.visitor");
class ModelClassVisitor extends abstract_visitor_1.AbstractFileVisitor {
constructor() {
super(...arguments);
this._typeImports = {};
this._collectedMetadata = {};
}
get typeImports() {
return this._typeImports;
}
get collectedMetadata() {
const metadataWithImports = [];
Object.keys(this._collectedMetadata).forEach((filePath) => {
const metadata = this._collectedMetadata[filePath];
const path = filePath.replace(/\.[jt]s$/, '');
const importExpr = ts.factory.createCallExpression(ts.factory.createToken(ts.SyntaxKind.ImportKeyword), undefined, [ts.factory.createStringLiteral(path)]);
metadataWithImports.push([importExpr, metadata]);
});
return metadataWithImports;
}
visit(sourceFile, ctx, program, options) {
const typeChecker = program.getTypeChecker();
sourceFile = this.updateImports(sourceFile, ctx.factory, program);
const propertyNodeVisitorFactory = (metadata) => (node) => {
const visit = () => {
if (ts.isPropertyDeclaration(node)) {
this.visitPropertyNodeDeclaration(node, ctx, typeChecker, options, sourceFile, metadata);
}
else if (options.parameterProperties &&
ts.isConstructorDeclaration(node)) {
this.visitConstructorDeclarationNode(node, typeChecker, options, sourceFile, metadata);
}
return node;
};
const visitedNode = visit();
if (!options.readonly) {
return visitedNode;
}
};
const visitClassNode = (node) => {
var _a;
if (ts.isClassDeclaration(node)) {
const metadata = {};
const isExported = (_a = node.modifiers) === null || _a === void 0 ? void 0 : _a.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
if (options.readonly) {
if (isExported) {
ts.forEachChild(node, propertyNodeVisitorFactory(metadata));
}
else {
if (options.debug) {
plugin_debug_logger_1.pluginDebugLogger.debug(`Skipping class "${node.name.getText()}" because it's not exported.`);
}
}
}
else {
node = ts.visitEachChild(node, propertyNodeVisitorFactory(metadata), ctx);
}
if ((isExported && options.readonly) || !options.readonly) {
const declaration = this.addMetadataFactory(ctx.factory, node, metadata, sourceFile, options);
if (!options.readonly) {
return declaration;
}
}
}
if (options.readonly) {
ts.forEachChild(node, visitClassNode);
}
else {
return ts.visitEachChild(node, visitClassNode, ctx);
}
};
return ts.visitNode(sourceFile, visitClassNode);
}
visitPropertyNodeDeclaration(node, ctx, typeChecker, options, sourceFile, metadata) {
const isPropertyStatic = (node.modifiers || []).some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword);
if (isPropertyStatic) {
return node;
}
const decorators = ts.canHaveDecorators(node) && ts.getDecorators(node);
const classTransformerShim = options.classTransformerShim;
const hidePropertyDecoratorExists = (0, plugin_utils_1.getDecoratorOrUndefinedByNames)(classTransformerShim
? [decorators_1.ApiHideProperty.name, 'Exclude']
: [decorators_1.ApiHideProperty.name], decorators, typescript_1.factory);
const annotatePropertyDecoratorExists = (0, plugin_utils_1.getDecoratorOrUndefinedByNames)(classTransformerShim ? [decorators_1.ApiProperty.name, 'Expose'] : [decorators_1.ApiProperty.name], decorators, typescript_1.factory);
if (!annotatePropertyDecoratorExists &&
(hidePropertyDecoratorExists || classTransformerShim === 'exclusive')) {
return node;
}
else if (annotatePropertyDecoratorExists && hidePropertyDecoratorExists) {
plugin_debug_logger_1.pluginDebugLogger.debug(`"${node.parent.name.getText()}->${node.name.getText()}" has conflicting decorators, excluding as @ApiHideProperty() takes priority.`);
return node;
}
try {
this.inspectPropertyDeclaration(ctx.factory, node, typeChecker, options, sourceFile.fileName, sourceFile, metadata);
}
catch (err) {
return node;
}
}
visitConstructorDeclarationNode(constructorNode, typeChecker, options, sourceFile, metadata) {
constructorNode.forEachChild((node) => {
if (ts.isParameter(node) &&
node.modifiers != null &&
node.modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.ReadonlyKeyword ||
modifier.kind === ts.SyntaxKind.PrivateKeyword ||
modifier.kind === ts.SyntaxKind.PublicKeyword ||
modifier.kind === ts.SyntaxKind.ProtectedKeyword)) {
const objectLiteralExpr = this.createDecoratorObjectLiteralExpr(typescript_1.factory, node, typeChecker, typescript_1.factory.createNodeArray(), options, sourceFile.fileName, sourceFile);
const propertyName = node.name.getText();
metadata[propertyName] = objectLiteralExpr;
}
});
}
addMetadataFactory(factory, node, classMetadata, sourceFile, options) {
const returnValue = factory.createObjectLiteralExpression(Object.keys(classMetadata).map((key) => factory.createPropertyAssignment(factory.createIdentifier(key), classMetadata[key])));
if (options.readonly) {
const filePath = this.normalizeImportPath(options.pathToSource, sourceFile.fileName);
if (!this._collectedMetadata[filePath]) {
this._collectedMetadata[filePath] = {};
}
const attributeKey = node.name.getText();
this._collectedMetadata[filePath][attributeKey] = returnValue;
return;
}
const method = factory.createMethodDeclaration([factory.createModifier(ts.SyntaxKind.StaticKeyword)], undefined, factory.createIdentifier(plugin_constants_1.METADATA_FACTORY_NAME), undefined, undefined, [], undefined, factory.createBlock([factory.createReturnStatement(returnValue)], true));
return factory.updateClassDeclaration(node, node.modifiers, node.name, node.typeParameters, node.heritageClauses, [...node.members, method]);
}
inspectPropertyDeclaration(factory, compilerNode, typeChecker, options, hostFilename, sourceFile, metadata) {
const objectLiteralExpr = this.createDecoratorObjectLiteralExpr(factory, compilerNode, typeChecker, factory.createNodeArray(), options, hostFilename, sourceFile);
this.addClassMetadata(compilerNode, objectLiteralExpr, sourceFile, metadata);
}
createDecoratorObjectLiteralExpr(factory, node, typeChecker, existingProperties = factory.createNodeArray(), options = {}, hostFilename = '', sourceFile) {
const isRequired = !node.questionToken;
const properties = [
...existingProperties,
!(0, plugin_utils_1.hasPropertyKey)('required', existingProperties) &&
factory.createPropertyAssignment('required', (0, ast_utils_1.createBooleanLiteral)(factory, isRequired)),
...this.createTypePropertyAssignments(factory, node.type, typeChecker, existingProperties, hostFilename, options),
...this.createDescriptionAndTsDocTagPropertyAssignments(factory, node, typeChecker, existingProperties, options, sourceFile),
this.createDefaultPropertyAssignment(factory, node, existingProperties, options),
this.createEnumPropertyAssignment(factory, node, typeChecker, existingProperties, hostFilename, options)
];
if ((ts.isPropertyDeclaration(node) || ts.isPropertySignature(node)) &&
options.classValidatorShim) {
properties.push(this.createValidationPropertyAssignments(factory, node, options));
}
return factory.createObjectLiteralExpression((0, lodash_1.compact)((0, lodash_1.flatten)(properties)));
}
createTypePropertyAssignments(factory, node, typeChecker, existingProperties, hostFilename, options) {
const key = 'type';
if ((0, plugin_utils_1.hasPropertyKey)(key, existingProperties)) {
return [];
}
if (node) {
if (ts.isTypeLiteralNode(node)) {
const initializer = this.createInitializerForTypeLiteralNode(node, factory, typeChecker, existingProperties, hostFilename, options);
return [factory.createPropertyAssignment(key, initializer)];
}
else if (ts.isUnionTypeNode(node)) {
const { nullableType, isNullable } = this.isNullableUnion(node);
const remainingTypes = node.types.filter((item) => item !== nullableType);
if (remainingTypes.length === 1) {
const propertyAssignments = this.createTypePropertyAssignments(factory, remainingTypes[0], typeChecker, existingProperties, hostFilename, options);
if (!isNullable) {
return propertyAssignments;
}
return [
...propertyAssignments,
factory.createPropertyAssignment('nullable', (0, ast_utils_1.createBooleanLiteral)(factory, true))
];
}
}
}
const type = typeChecker.getTypeAtLocation(node);
if (!type) {
return [];
}
const typeReferenceDescriptor = (0, plugin_utils_1.getTypeReferenceAsString)(type, typeChecker);
if (!typeReferenceDescriptor.typeName) {
return [];
}
const identifier = (0, type_reference_to_identifier_util_1.typeReferenceToIdentifier)(typeReferenceDescriptor, hostFilename, options, factory, type, this._typeImports);
const initializer = factory.createArrowFunction(undefined, undefined, [], undefined, undefined, identifier);
return [factory.createPropertyAssignment(key, initializer)];
}
createInitializerForTypeLiteralNode(node, factory, typeChecker, existingProperties, hostFilename, options) {
const propertyAssignments = Array.from(node.members || []).map((member) => {
const literalExpr = this.createDecoratorObjectLiteralExpr(factory, member, typeChecker, existingProperties, options, hostFilename);
return factory.createPropertyAssignment(factory.createIdentifier(member.name.getText()), literalExpr);
});
const initializer = factory.createArrowFunction(undefined, undefined, [], undefined, undefined, factory.createParenthesizedExpression(factory.createObjectLiteralExpression(propertyAssignments)));
return initializer;
}
isNullableUnion(node) {
const nullableType = node.types.find((type) => type.kind === ts.SyntaxKind.NullKeyword ||
(ts.SyntaxKind.LiteralType && type.getText() === 'null'));
const isNullable = !!nullableType;
return { nullableType, isNullable };
}
createEnumPropertyAssignment(factory, node, typeChecker, existingProperties, hostFilename, options) {
const key = 'enum';
if ((0, plugin_utils_1.hasPropertyKey)(key, existingProperties)) {
return undefined;
}
let type = typeChecker.getTypeAtLocation(node);
if (!type) {
return undefined;
}
if ((0, plugin_utils_1.isAutoGeneratedTypeUnion)(type)) {
const types = type.types;
type = types[types.length - 1];
}
const typeIsArrayTuple = (0, plugin_utils_1.extractTypeArgumentIfArray)(type);
if (!typeIsArrayTuple) {
return undefined;
}
let isArrayType = typeIsArrayTuple.isArray;
type = typeIsArrayTuple.type;
const isEnumMember = type.symbol && type.symbol.flags === ts.SymbolFlags.EnumMember;
if (!(0, ast_utils_1.isEnum)(type) || isEnumMember) {
if (!isEnumMember) {
type = (0, plugin_utils_1.isAutoGeneratedEnumUnion)(type, typeChecker);
}
if (!type) {
return undefined;
}
const typeIsArrayTuple = (0, plugin_utils_1.extractTypeArgumentIfArray)(type);
if (!typeIsArrayTuple) {
return undefined;
}
isArrayType = typeIsArrayTuple.isArray;
type = typeIsArrayTuple.type;
}
const typeReferenceDescriptor = { typeName: (0, ast_utils_1.getText)(type, typeChecker) };
const enumIdentifier = (0, type_reference_to_identifier_util_1.typeReferenceToIdentifier)(typeReferenceDescriptor, hostFilename, options, factory, type, this._typeImports);
const enumProperty = factory.createPropertyAssignment(key, enumIdentifier);
if (isArrayType) {
const isArrayKey = 'isArray';
const isArrayProperty = factory.createPropertyAssignment(isArrayKey, factory.createIdentifier('true'));
return [enumProperty, isArrayProperty];
}
return enumProperty;
}
createDefaultPropertyAssignment(factory, node, existingProperties, options) {
var _a;
const key = 'default';
if ((0, plugin_utils_1.hasPropertyKey)(key, existingProperties)) {
return undefined;
}
if (ts.isPropertySignature(node)) {
return undefined;
}
if (node.initializer == null) {
return undefined;
}
let initializer = node.initializer;
if (ts.isAsExpression(initializer)) {
initializer = initializer.expression;
}
initializer =
(_a = this.clonePrimitiveLiteral(factory, initializer)) !== null && _a !== void 0 ? _a : initializer;
if (!(0, plugin_utils_1.canReferenceNode)(initializer, options)) {
const parentFilePath = node.getSourceFile().fileName;
const propertyName = node.name.getText();
plugin_debug_logger_1.pluginDebugLogger.debug(`Skipping registering default value for "${propertyName}" property in "${parentFilePath}" file because it is not a referenceable value ("${initializer.getText()}").`);
return undefined;
}
return factory.createPropertyAssignment(key, initializer);
}
createValidationPropertyAssignments(factory, node, options) {
const assignments = [];
const decorators = ts.canHaveDecorators(node) && ts.getDecorators(node);
if (!options.readonly) {
this.addPropertyByValidationDecorator(factory, 'IsIn', 'enum', decorators, assignments, options);
}
this.addPropertyByValidationDecorator(factory, 'Min', 'minimum', decorators, assignments, options);
this.addPropertyByValidationDecorator(factory, 'Max', 'maximum', decorators, assignments, options);
this.addPropertyByValidationDecorator(factory, 'MinLength', 'minLength', decorators, assignments, options);
this.addPropertyByValidationDecorator(factory, 'MaxLength', 'maxLength', decorators, assignments, options);
this.addPropertiesByValidationDecorator(factory, 'IsPositive', decorators, assignments, () => {
return [
factory.createPropertyAssignment('minimum', (0, ast_utils_1.createPrimitiveLiteral)(factory, 1))
];
});
this.addPropertiesByValidationDecorator(factory, 'IsNegative', decorators, assignments, () => {
return [
factory.createPropertyAssignment('maximum', (0, ast_utils_1.createPrimitiveLiteral)(factory, -1))
];
});
this.addPropertiesByValidationDecorator(factory, 'Length', decorators, assignments, (decoratorRef) => {
var _a, _b;
const decoratorArguments = (0, ast_utils_1.getDecoratorArguments)(decoratorRef);
const result = [];
const minLength = (0, lodash_1.head)(decoratorArguments);
if (!(0, plugin_utils_1.canReferenceNode)(minLength, options)) {
return result;
}
const clonedMinLength = (_a = this.clonePrimitiveLiteral(factory, minLength)) !== null && _a !== void 0 ? _a : minLength;
if (clonedMinLength) {
result.push(factory.createPropertyAssignment('minLength', clonedMinLength));
}
if (decoratorArguments.length > 1) {
const maxLength = decoratorArguments[1];
if (!(0, plugin_utils_1.canReferenceNode)(maxLength, options)) {
return result;
}
const clonedMaxLength = (_b = this.clonePrimitiveLiteral(factory, maxLength)) !== null && _b !== void 0 ? _b : maxLength;
if (clonedMaxLength) {
result.push(factory.createPropertyAssignment('maxLength', clonedMaxLength));
}
}
return result;
});
this.addPropertiesByValidationDecorator(factory, 'Matches', decorators, assignments, (decoratorRef) => {
const decoratorArguments = (0, ast_utils_1.getDecoratorArguments)(decoratorRef);
return [
factory.createPropertyAssignment('pattern', (0, ast_utils_1.createPrimitiveLiteral)(factory, (0, lodash_1.head)(decoratorArguments).text))
];
});
return assignments;
}
addPropertyByValidationDecorator(factory, decoratorName, propertyKey, decorators, assignments, options) {
this.addPropertiesByValidationDecorator(factory, decoratorName, decorators, assignments, (decoratorRef) => {
var _a;
const argument = (0, lodash_1.head)((0, ast_utils_1.getDecoratorArguments)(decoratorRef));
const assignment = (_a = this.clonePrimitiveLiteral(factory, argument)) !== null && _a !== void 0 ? _a : argument;
if (!(0, plugin_utils_1.canReferenceNode)(assignment, options)) {
return [];
}
return [factory.createPropertyAssignment(propertyKey, assignment)];
});
}
addPropertiesByValidationDecorator(factory, decoratorName, decorators, assignments, addPropertyAssignments) {
const decoratorRef = (0, plugin_utils_1.getDecoratorOrUndefinedByNames)([decoratorName], decorators, factory);
if (!decoratorRef) {
return;
}
assignments.push(...addPropertyAssignments(decoratorRef));
}
addClassMetadata(node, objectLiteral, sourceFile, metadata) {
const hostClass = node.parent;
const className = hostClass.name && hostClass.name.getText();
if (!className) {
return;
}
const propertyName = node.name && node.name.getText(sourceFile);
if (!propertyName ||
(node.name && node.name.kind === ts.SyntaxKind.ComputedPropertyName)) {
return;
}
metadata[propertyName] = objectLiteral;
}
createDescriptionAndTsDocTagPropertyAssignments(factory, node, typeChecker, existingProperties = factory.createNodeArray(), options = {}, sourceFile) {
var _a;
if (!options.introspectComments || !sourceFile) {
return [];
}
const propertyAssignments = [];
const comments = (0, ast_utils_1.getMainCommentOfNode)(node, sourceFile);
const tags = (0, ast_utils_1.getTsDocTagsOfNode)(node, typeChecker);
const keyOfComment = options.dtoKeyOfComment;
if (!(0, plugin_utils_1.hasPropertyKey)(keyOfComment, existingProperties) && comments) {
const descriptionPropertyAssignment = factory.createPropertyAssignment(keyOfComment, factory.createStringLiteral(comments));
propertyAssignments.push(descriptionPropertyAssignment);
}
const hasExampleOrExamplesKey = (0, plugin_utils_1.hasPropertyKey)('example', existingProperties) ||
(0, plugin_utils_1.hasPropertyKey)('examples', existingProperties);
if (!hasExampleOrExamplesKey && ((_a = tags.example) === null || _a === void 0 ? void 0 : _a.length)) {
if (tags.example.length === 1) {
const examplePropertyAssignment = factory.createPropertyAssignment('example', (0, ast_utils_1.createLiteralFromAnyValue)(factory, tags.example[0]));
propertyAssignments.push(examplePropertyAssignment);
}
else {
const examplesPropertyAssignment = factory.createPropertyAssignment('examples', (0, ast_utils_1.createLiteralFromAnyValue)(factory, tags.example));
propertyAssignments.push(examplesPropertyAssignment);
}
}
const hasDeprecatedKey = (0, plugin_utils_1.hasPropertyKey)('deprecated', existingProperties);
if (!hasDeprecatedKey && tags.deprecated) {
const deprecatedPropertyAssignment = factory.createPropertyAssignment('deprecated', (0, ast_utils_1.createLiteralFromAnyValue)(factory, tags.deprecated));
propertyAssignments.push(deprecatedPropertyAssignment);
}
return propertyAssignments;
}
normalizeImportPath(pathToSource, path) {
let relativePath = path_1.posix.relative((0, plugin_utils_1.convertPath)(pathToSource), (0, plugin_utils_1.convertPath)(path));
relativePath = relativePath[0] !== '.' ? './' + relativePath : relativePath;
return relativePath;
}
clonePrimitiveLiteral(factory, node) {
var _a;
const primitiveTypeName = this.getInitializerPrimitiveTypeName(node);
if (!primitiveTypeName) {
return undefined;
}
const text = (_a = node.text) !== null && _a !== void 0 ? _a : node.getText();
return (0, ast_utils_1.createPrimitiveLiteral)(factory, text, primitiveTypeName);
}
getInitializerPrimitiveTypeName(node) {
if (ts.isIdentifier(node) &&
(node.text === 'true' || node.text === 'false')) {
return 'boolean';
}
if (ts.isNumericLiteral(node) || ts.isPrefixUnaryExpression(node)) {
return 'number';
}
if (ts.isStringLiteral(node)) {
return 'string';
}
return undefined;
}
}
exports.ModelClassVisitor = ModelClassVisitor;

View File

@@ -0,0 +1,21 @@
import * as ts from 'typescript';
import { PluginOptions } from '../merge-options';
export declare class ReadonlyVisitor {
private readonly options;
readonly key = "@nestjs/swagger";
private readonly modelClassVisitor;
private readonly controllerClassVisitor;
get typeImports(): {
[x: string]: string;
};
constructor(options: PluginOptions);
visit(program: ts.Program, sf: ts.SourceFile): ts.Node;
collect(): {
models: [ts.CallExpression, Record<string, {
[x: string]: ts.ObjectLiteralExpression;
}>][];
controllers: [ts.CallExpression, Record<string, {
[x: string]: ts.ObjectLiteralExpression;
}>][];
};
}

View File

@@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReadonlyVisitor = void 0;
const ts = require("typescript");
const merge_options_1 = require("../merge-options");
const is_filename_matched_util_1 = require("../utils/is-filename-matched.util");
const controller_class_visitor_1 = require("./controller-class.visitor");
const model_class_visitor_1 = require("./model-class.visitor");
class ReadonlyVisitor {
get typeImports() {
return Object.assign(Object.assign({}, this.modelClassVisitor.typeImports), this.controllerClassVisitor.typeImports);
}
constructor(options) {
this.options = options;
this.key = '@nestjs/swagger';
this.modelClassVisitor = new model_class_visitor_1.ModelClassVisitor();
this.controllerClassVisitor = new controller_class_visitor_1.ControllerClassVisitor();
options.readonly = true;
if (!options.pathToSource) {
throw new Error(`"pathToSource" must be defined in plugin options`);
}
}
visit(program, sf) {
const factoryHost = { factory: ts.factory };
const parsedOptions = (0, merge_options_1.mergePluginOptions)(this.options);
if ((0, is_filename_matched_util_1.isFilenameMatched)(parsedOptions.dtoFileNameSuffix, sf.fileName)) {
return this.modelClassVisitor.visit(sf, factoryHost, program, parsedOptions);
}
if ((0, is_filename_matched_util_1.isFilenameMatched)(parsedOptions.controllerFileNameSuffix, sf.fileName)) {
return this.controllerClassVisitor.visit(sf, factoryHost, program, parsedOptions);
}
}
collect() {
return {
models: this.modelClassVisitor.collectedMetadata,
controllers: this.controllerClassVisitor.collectedMetadata
};
}
}
exports.ReadonlyVisitor = ReadonlyVisitor;