@protobuf-ts/plugin-framework
Version:
framework to create protoc plugins
207 lines (206 loc) • 7.99 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypescriptImportManager = void 0;
const runtime_1 = require("@protobuf-ts/runtime");
const ts = require("typescript");
const path = require("path");
/** @deprecated */
class TypescriptImportManager {
constructor(generatedFile, symbols, source) {
this.file = generatedFile;
this.symbols = symbols;
this.source = source;
}
/**
* Import {importName} from "importFrom";
*
* Automatically finds a free name if the
* `importName` would collide with another
* identifier.
*
* Returns imported name.
*/
name(importName, importFrom) {
const blackListedNames = this.symbols.list(this.file).map(e => e.name);
return ensureNamedImportPresent(this.source.getSourceFile(), importName, importFrom, blackListedNames, statementToAdd => this.source.addStatement(statementToAdd, true));
}
/**
* Import * as importAs from "importFrom";
*
* Returns name for `importAs`.
*/
namespace(importAs, importFrom) {
return ensureNamespaceImportPresent(this.source.getSourceFile(), importAs, importFrom, statementToAdd => this.source.addStatement(statementToAdd, true));
}
/**
* Import a previously registered identifier for a message
* or other descriptor.
*
* Uses the symbol table to look for the type, adds an
* import statement if necessary and automatically finds a
* free name if the identifier would clash in this file.
*
* If you have multiple representations for a descriptor
* in your generated code, use `kind` to discriminate.
*/
type(descriptor, kind = 'default') {
const symbolReg = this.symbols.get(descriptor, kind);
// symbol in this file?
if (symbolReg.file === this.file) {
return symbolReg.name;
}
// symbol not in file
// add an import statement
const importPath = createRelativeImportPath(this.source.getSourceFile().fileName, symbolReg.file.getFilename());
const blackListedNames = this.symbols.list(this.file).map(e => e.name);
return ensureNamedImportPresent(this.source.getSourceFile(), symbolReg.name, importPath, blackListedNames, statementToAdd => this.source.addStatement(statementToAdd, true));
}
}
exports.TypescriptImportManager = TypescriptImportManager;
/**
* Import * as asName from "importFrom";
*
* If the import is already present, just return the
* identifier.
*
* If the import is not present, create the import
* statement and call `addStatementFn`.
*
* Does *not* check for collisions.
*/
function ensureNamespaceImportPresent(currentFile, asName, importFrom, addStatementFn) {
const all = findNamespaceImports(currentFile), match = all.find(ni => ni.as === asName && ni.from === importFrom);
if (match) {
return match.as;
}
const statementToAdd = createNamespaceImport(asName, importFrom);
addStatementFn(statementToAdd);
return asName;
}
/**
* import * as <asName> from "<importFrom>";
*/
function createNamespaceImport(asName, importFrom) {
return ts.createImportDeclaration(undefined, undefined, ts.createImportClause(undefined, ts.createNamespaceImport(ts.createIdentifier(asName))), ts.createStringLiteral(importFrom));
}
/**
* import * as <as> from "<from>";
*/
function findNamespaceImports(sourceFile) {
let r = [];
for (let s of sourceFile.statements) {
if (ts.isImportDeclaration(s) && s.importClause) {
let namedBindings = s.importClause.namedBindings;
if (namedBindings && ts.isNamespaceImport(namedBindings)) {
runtime_1.assert(ts.isStringLiteral(s.moduleSpecifier));
r.push({
as: namedBindings.name.escapedText.toString(),
from: s.moduleSpecifier.text
});
}
}
}
return r;
}
/**
* Import {importName} from "importFrom";
*
* If the import is already present, just return the
* identifier.
*
* If the import is not present, create the import
* statement and call `addStatementFn`.
*
* If the import name is taken by another named import
* or is in the list of blacklisted names, an
* alternative name is used:
*
* Import {importName as alternativeName} from "importFrom";
*
* Returns the imported name or the alternative name.
*/
function ensureNamedImportPresent(currentFile, importName, importFrom, blacklistedNames, addStatementFn, escapeCharacter = '$') {
var _a;
const all = findNamedImports(currentFile), taken = all.map(ni => { var _a; return (_a = ni.as) !== null && _a !== void 0 ? _a : ni.name; }).concat(blacklistedNames), match = all.find(ni => ni.name === importName && ni.from === importFrom);
if (match) {
return (_a = match.as) !== null && _a !== void 0 ? _a : match.name;
}
let as;
if (taken.includes(importName)) {
let i = 0;
as = importName;
while (taken.includes(as)) {
as = importName + escapeCharacter;
if (i++ > 0) {
as += i;
}
}
}
const statementToAdd = createNamedImport(importName, importFrom, as);
addStatementFn(statementToAdd);
return as !== null && as !== void 0 ? as : importName;
}
/**
* import {<name>} from '<from>';
* import {<name> as <as>} from '<from>';
*/
function createNamedImport(name, from, as) {
if (as) {
return ts.createImportDeclaration(undefined, undefined, ts.createImportClause(undefined, ts.createNamedImports([ts.createImportSpecifier(ts.createIdentifier(name), ts.createIdentifier(as))]), false), ts.createStringLiteral(from));
}
return ts.createImportDeclaration(undefined, undefined, ts.createImportClause(undefined, ts.createNamedImports([
ts.createImportSpecifier(undefined, ts.createIdentifier(name))
])), ts.createStringLiteral(from));
}
/**
* import {<name>} from '<from>';
* import {<name> as <as>} from '<from>';
*/
function findNamedImports(sourceFile) {
let r = [];
for (let s of sourceFile.statements) {
if (ts.isImportDeclaration(s) && s.importClause) {
let namedBindings = s.importClause.namedBindings;
if (namedBindings && ts.isNamedImports(namedBindings)) {
for (let importSpecifier of namedBindings.elements) {
runtime_1.assert(ts.isStringLiteral(s.moduleSpecifier));
if (importSpecifier.propertyName) {
r.push({
name: importSpecifier.propertyName.escapedText.toString(),
as: importSpecifier.name.escapedText.toString(),
from: s.moduleSpecifier.text
});
}
else {
r.push({
name: importSpecifier.name.escapedText.toString(),
as: undefined,
from: s.moduleSpecifier.text
});
}
}
}
}
}
return r;
}
/**
* Create a relative path for an import statement like
* `import {Foo} from "./foo"`
*/
function createRelativeImportPath(currentPath, pathToImportFrom) {
// create relative path to the file to import
let fromPath = path.relative(path.dirname(currentPath), pathToImportFrom);
// on windows, this may add backslash directory separators.
// we replace them with forward slash.
if (path.sep !== "/") {
fromPath = fromPath.split(path.sep).join("/");
}
// drop file extension
fromPath = fromPath.replace(/\.[a-z]+$/, '');
// make sure to start with './' to signal relative path to module resolution
if (!fromPath.startsWith('../') && !fromPath.startsWith('./')) {
fromPath = './' + fromPath;
}
return fromPath;
}