plaxtony
Version:
Static code analysis of SC2 Galaxy Script
447 lines • 18.5 kB
JavaScript
;
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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SC2Workspace = exports.S2ArchiveNsTypeKind = exports.S2ArchiveNsNameKind = exports.SC2Archive = exports.openArchiveWorkspace = exports.resolveArchiveDependencyList = exports.resolveArchiveDirectory = exports.LocalizationComponent = exports.CatalogComponent = exports.TriggerComponent = exports.Component = exports.findSC2ArchiveDirectories = exports.isSC2Archive = exports.BuiltinDeps = void 0;
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
const vscode_uri_1 = require("vscode-uri");
const fs = require("fs-extra");
const util = require("util");
const path = require("path");
const xml = require("xml2js");
const glob = require("fast-glob");
const trig = require("./trigger");
const cat = require("./datacatalog");
const loc = require("./localization");
const common_1 = require("../common");
const validArchiveExtensions = [
'sc2map',
'sc2interface',
'sc2campaign',
'sc2mod',
];
const reValidArchiveExtension = new RegExp('\\.(' + validArchiveExtensions.join('|') + ')$', 'i');
var BuiltinDeps;
(function (BuiltinDeps) {
BuiltinDeps[BuiltinDeps["mods/core.sc2mod"] = 0] = "mods/core.sc2mod";
BuiltinDeps[BuiltinDeps["mods/glue.sc2mod"] = 1] = "mods/glue.sc2mod";
BuiltinDeps[BuiltinDeps["mods/liberty.sc2mod"] = 2] = "mods/liberty.sc2mod";
BuiltinDeps[BuiltinDeps["mods/swarm.sc2mod"] = 3] = "mods/swarm.sc2mod";
BuiltinDeps[BuiltinDeps["mods/void.sc2mod"] = 4] = "mods/void.sc2mod";
BuiltinDeps[BuiltinDeps["mods/libertymulti.sc2mod"] = 5] = "mods/libertymulti.sc2mod";
BuiltinDeps[BuiltinDeps["mods/swarmmulti.sc2mod"] = 6] = "mods/swarmmulti.sc2mod";
BuiltinDeps[BuiltinDeps["mods/voidmulti.sc2mod"] = 7] = "mods/voidmulti.sc2mod";
BuiltinDeps[BuiltinDeps["mods/balancemulti.sc2mod"] = 8] = "mods/balancemulti.sc2mod";
BuiltinDeps[BuiltinDeps["mods/starcoop/starcoop.sc2mod"] = 9] = "mods/starcoop/starcoop.sc2mod";
BuiltinDeps[BuiltinDeps["mods/war3.sc2mod"] = 10] = "mods/war3.sc2mod";
BuiltinDeps[BuiltinDeps["mods/novastoryassets.sc2mod"] = 11] = "mods/novastoryassets.sc2mod";
BuiltinDeps[BuiltinDeps["campaigns/liberty.sc2campaign"] = 12] = "campaigns/liberty.sc2campaign";
BuiltinDeps[BuiltinDeps["campaigns/swarm.sc2campaign"] = 13] = "campaigns/swarm.sc2campaign";
BuiltinDeps[BuiltinDeps["campaigns/void.sc2campaign"] = 14] = "campaigns/void.sc2campaign";
BuiltinDeps[BuiltinDeps["campaigns/libertystory.sc2campaign"] = 15] = "campaigns/libertystory.sc2campaign";
BuiltinDeps[BuiltinDeps["campaigns/swarmstory.sc2campaign"] = 16] = "campaigns/swarmstory.sc2campaign";
BuiltinDeps[BuiltinDeps["campaigns/voidstory.sc2campaign"] = 17] = "campaigns/voidstory.sc2campaign";
})(BuiltinDeps = exports.BuiltinDeps || (exports.BuiltinDeps = {}));
const builtinDepsHierarchy = (function () {
function depsFor(modName) {
let list = [];
let matches;
if (matches = /^campaigns\/(liberty|swarm|void)story\.sc2campaign$/i.exec(modName)) {
list.push('campaigns/' + matches[1] + '.sc2campaign');
}
else if (matches = /^mods\/(liberty|swarm|void|balance)multi\.sc2mod$/i.exec(modName)) {
if (matches[1] === 'balance') {
list.push('mods/void.sc2mod');
}
else {
list.push('mods/' + matches[1] + '.sc2mod');
}
}
else if (matches = /^campaigns\/(liberty|swarm|void)\.sc2campaign$/i.exec(modName)) {
if (matches[1] === 'void') {
list.push('campaigns/swarm.sc2campaign');
}
else if (matches[1] === 'swarm') {
list.push('campaigns/liberty.sc2campaign');
}
list.push('mods/' + matches[1] + '.sc2mod');
}
else if (matches = /^mods\/(liberty|swarm|void)\.sc2mod$/i.exec(modName)) {
if (matches[1] === 'void') {
list.push('mods/swarm.sc2mod');
}
else if (matches[1] === 'swarm') {
list.push('mods/liberty.sc2mod');
}
}
else {
switch (modName) {
case 'mods/novastoryassets.sc2mod':
case 'mods/starcoop/starcoop.sc2mod':
{
list.push('campaigns/void.sc2campaign');
break;
}
}
}
for (const item of list) {
list = depsFor(item).concat(list.reverse());
}
return list;
}
const depHierarchy = {};
for (const modName of Object.keys(BuiltinDeps).filter(v => typeof BuiltinDeps[v] === 'number')) {
depHierarchy[modName] = [];
if (modName !== 'mods/core.sc2mod') {
depHierarchy[modName].push('mods/core.sc2mod');
}
depHierarchy[modName] = depHierarchy[modName].concat(Array.from(new Set(depsFor(modName))));
}
return depHierarchy;
})();
function isSC2Archive(directory) {
return path.basename(directory).match(reValidArchiveExtension);
}
exports.isSC2Archive = isSC2Archive;
async function findSC2ArchiveDirectories(directory, opts = {}) {
directory = path.resolve(directory);
if (isSC2Archive(directory)) {
return [directory];
}
const results = (await glob(`**/*.{${validArchiveExtensions.join(',')}}`, {
caseSensitiveMatch: false,
absolute: true,
cwd: directory,
ignore: opts.exclude,
onlyDirectories: true,
stats: true,
objectMode: true,
})).map(x => x.path);
return results.sort((a, b) => {
return (validArchiveExtensions.indexOf(b.match(reValidArchiveExtension)[1].toLowerCase()) -
validArchiveExtensions.indexOf(a.match(reValidArchiveExtension)[1].toLowerCase()));
});
}
exports.findSC2ArchiveDirectories = findSC2ArchiveDirectories;
class Component {
constructor(workspace) {
this.workspace = workspace;
}
async load() {
return await this.loadData();
}
}
exports.Component = Component;
class TriggerComponent extends Component {
constructor() {
super(...arguments);
this.store = new trig.TriggerStore();
}
// protected libraries: trig.Library;
async loadData() {
const trigReader = new trig.XMLReader(this.store);
for (const archive of this.workspace.metadataArchives) {
for (const filename of await archive.findFiles('**/*.{TriggerLib,SC2Lib}')) {
common_1.logger.debug(`:: ${archive.name}/${filename}`);
this.store.addLibrary(await trigReader.loadLibrary(await archive.readFile(filename)));
}
if (await archive.hasFile('Triggers')) {
common_1.logger.debug(`:: ${archive.name}/Triggers`);
await trigReader.load(await archive.readFile('Triggers'), this.workspace.rootArchive !== archive);
}
}
return true;
}
getStore() {
return this.store;
}
}
__decorate([
common_1.logIt()
], TriggerComponent.prototype, "loadData", null);
exports.TriggerComponent = TriggerComponent;
class CatalogComponent extends Component {
constructor() {
super(...arguments);
this.store = new cat.CatalogStore();
}
async loadData() {
for await (const [archive, filename] of this.workspace.findFiles('**/GameData/**/*.xml')) {
common_1.logger.debug(`:: ${archive.name}/${filename}`);
const doc = await SC2Workspace.documentFromFile(archive, filename, 'xml');
this.store.update(doc, archive);
}
return true;
}
getStore() {
return this.store;
}
}
__decorate([
common_1.logIt()
], CatalogComponent.prototype, "loadData", null);
exports.CatalogComponent = CatalogComponent;
class LocalizationComponent extends Component {
constructor() {
super(...arguments);
this.lang = 'enUS';
this.triggers = new loc.LocalizationTriggers();
this.strings = new Map();
}
async loadStrings(name) {
const textStore = new loc.LocalizationTextStore();
for (const archive of this.workspace.metadataArchives) {
const filenames = await archive.findFiles('**/' + this.lang + '.SC2Data/LocalizedData/' + name + 'Strings.txt');
if (filenames.length) {
common_1.logger.debug(`:: ${archive.name}/${filenames[0]}`);
const locFile = new loc.LocalizationFile();
locFile.read(await archive.readFile(filenames[0]));
textStore.merge(locFile);
}
}
this.strings.set(name, textStore);
}
async loadData() {
for (const archive of this.workspace.metadataArchives) {
const filenames = await archive.findFiles('**/' + this.lang + '.SC2Data/LocalizedData/TriggerStrings.txt');
if (filenames.length) {
common_1.logger.debug(`:: ${archive.name}/${filenames[0]}`);
const locFile = new loc.LocalizationFile();
locFile.read(await archive.readFile(filenames[0]));
this.triggers.merge(locFile);
}
}
// await this.loadStrings('Trigger');
await this.loadStrings('Game');
await this.loadStrings('Object');
return true;
}
}
__decorate([
common_1.logIt()
], LocalizationComponent.prototype, "loadData", null);
exports.LocalizationComponent = LocalizationComponent;
async function resolveArchiveDirectory(name, sources) {
for (const src of sources) {
const results = await glob(`**/${name}`, {
caseSensitiveMatch: false,
absolute: true,
cwd: src,
onlyDirectories: true,
});
if (results.length) {
return results[0];
}
}
}
exports.resolveArchiveDirectory = resolveArchiveDirectory;
async function resolveArchiveDependencyList(rootArchive, sources, opts = {}) {
const list = [];
const unresolvedNames = [];
async function resolveWorker(archive) {
for (const entry of await archive.getDependencyList()) {
if (list.findIndex((item) => item.name === entry) !== -1) {
continue;
}
const link = {
name: entry,
};
let dir;
if (opts.overrides && opts.overrides.has(entry)) {
dir = opts.overrides.get(entry);
}
else {
dir = await resolveArchiveDirectory(entry, sources);
}
if (!dir && opts.fallbackResolve) {
dir = await opts.fallbackResolve(entry);
}
if (dir) {
await resolveWorker(new SC2Archive(entry, dir));
link.src = dir;
list.push(link);
}
else {
unresolvedNames.push(entry);
}
}
}
await resolveWorker(rootArchive);
return {
list,
unresolvedNames,
};
}
exports.resolveArchiveDependencyList = resolveArchiveDependencyList;
async function openArchiveWorkspace(archive, sources, overrides = null, extra = null) {
const dependencyArchives = [];
const result = await resolveArchiveDependencyList(archive, sources, {
overrides,
});
if (result.unresolvedNames.length > 0) {
throw new Error(`couldn\'t resolve ${util.inspect(result.unresolvedNames)}\nSources: ${util.inspect(sources)}\nOverrides: ${util.inspect(overrides)}`);
}
for (const link of result.list) {
dependencyArchives.push(new SC2Archive(link.name, link.src));
}
if (extra) {
for (const [name, src] of extra) {
dependencyArchives.push(new SC2Archive(name, src));
}
}
return new SC2Workspace(archive, dependencyArchives);
}
exports.openArchiveWorkspace = openArchiveWorkspace;
class SC2Archive {
constructor(name = null, directory) {
this.priority = 0;
if (name === null) {
name = path.basename(directory);
}
this.name = name.replace(/\\/g, '/').toLowerCase();
this.directory = fs.realpathSync(path.resolve(directory));
this.lcFsPath = this.directory.toLowerCase();
}
async findFiles(pattern) {
return glob(pattern, {
cwd: this.directory,
caseSensitiveMatch: false,
onlyFiles: true,
objectMode: false,
ignore: [
'base{0..99}.sc2maps/**',
],
});
}
async hasFile(filename) {
return fs.pathExists(path.join(this.directory, filename));
}
async readFile(filename) {
return fs.readFile(path.join(this.directory, filename), 'utf8');
}
relativePath(uri) {
const fsPath = vscode_uri_1.default.parse(uri).fsPath;
if (fsPath.substring(0, this.lcFsPath.length).toLowerCase() !== this.lcFsPath)
return;
const relativeFsPath = fsPath.substring(this.lcFsPath.length + 1);
return relativeFsPath;
}
/**
* returns lowercased and forward slash normalized list
*/
async getDependencyList() {
let list = [];
if (builtinDepsHierarchy[this.name]) {
list = list.concat(builtinDepsHierarchy[this.name]);
}
if (await this.hasFile('DocumentInfo')) {
try {
const content = await this.readFile('DocumentInfo');
const data = await xml.parseStringPromise(content);
for (const depValue of data.DocInfo.Dependencies[0].Value) {
list.push(depValue.substr(depValue.indexOf('file:') + 5).replace(/\\/g, '/').toLowerCase());
}
}
catch (err) {
common_1.logger.warn(`Couldn't read dependencies from "DocumentInfo" of "${this.name}`, err.message);
list.push('mods/core.sc2mod');
}
}
return list;
}
get isBuiltin() {
return builtinDepsHierarchy[this.name] !== void 0;
}
}
exports.SC2Archive = SC2Archive;
var S2ArchiveNsNameKind;
(function (S2ArchiveNsNameKind) {
S2ArchiveNsNameKind[S2ArchiveNsNameKind["base"] = 0] = "base";
S2ArchiveNsNameKind[S2ArchiveNsNameKind["enus"] = 1] = "enus";
// TODO: add missing localizations
})(S2ArchiveNsNameKind = exports.S2ArchiveNsNameKind || (exports.S2ArchiveNsNameKind = {}));
var S2ArchiveNsTypeKind;
(function (S2ArchiveNsTypeKind) {
S2ArchiveNsTypeKind[S2ArchiveNsTypeKind["sc2assets"] = 0] = "sc2assets";
S2ArchiveNsTypeKind[S2ArchiveNsTypeKind["sc2data"] = 1] = "sc2data";
})(S2ArchiveNsTypeKind = exports.S2ArchiveNsTypeKind || (exports.S2ArchiveNsTypeKind = {}));
const reArchiveFileNs = /^(?:(?<nsName>[a-z]+)\.(?<nsType>(?:sc2data|sc2assets))(?:\/|\\))?(?<rp>.+)$/i;
const reIsUri = /^[^:/?#]+:\/\//i;
class SC2Workspace {
constructor(rootArchive, dependencies = []) {
this.allArchives = [];
this.dependencies = [];
this.metadataArchives = [];
this.trigComponent = new TriggerComponent(this);
this.locComponent = new LocalizationComponent(this);
this.catalogComponent = new CatalogComponent(this);
this.arvMap = new Map();
this.rootArchive = rootArchive;
this.dependencies = dependencies;
this.allArchives = this.allArchives.concat(this.dependencies);
this.metadataArchives = this.allArchives;
if (rootArchive) {
this.allArchives.push(rootArchive);
}
this.allArchives.forEach((av, index) => {
this.arvMap.set(av.name, av);
av.priority = index * 20;
});
}
async *findFiles(pattern) {
const stuff = this.allArchives.map(archive => [archive, archive.findFiles(pattern)]);
for (const [archive, archiveFiles] of stuff) {
for (const filename of await archiveFiles) {
yield [archive, filename];
}
}
}
static async documentFromFile(archive, filename, languageId) {
if (!languageId) {
languageId = path.extname(filename).split('.').pop();
}
return vscode_languageserver_textdocument_1.TextDocument.create(vscode_uri_1.default.file(path.join(archive.directory, filename)).toString(), languageId, 0, await archive.readFile(filename));
}
resolvePath(fsPath) {
if (fsPath.match(reIsUri)) {
fsPath = vscode_uri_1.default.parse(fsPath).fsPath;
}
for (const cArchive of this.allArchives) {
if (!fsPath.toLowerCase().startsWith(cArchive.lcFsPath))
continue;
const m = fsPath.substring(cArchive.directory.length + 1).match(reArchiveFileNs);
if (!m)
return;
let priority = cArchive.priority;
let ns;
if (m.groups['nsName']) {
ns = {
name: m.groups['nsName'].toLowerCase(),
type: m.groups['nsType'].toLowerCase(),
};
priority += S2ArchiveNsTypeKind[ns.type];
if (ns.name !== 'base') {
priority += 5;
}
}
else {
priority += 10;
}
const relativePath = m.groups['rp'].replace(/\\/g, '/');
return {
fsPath: fsPath,
relativePath: relativePath,
archiveRelpath: m.groups['nsName'] ? `${m.groups['nsName']}.${m.groups['nsType']}/${relativePath}` : relativePath,
namespace: ns,
archive: cArchive,
priority: priority,
};
}
}
}
exports.SC2Workspace = SC2Workspace;
//# sourceMappingURL=archive.js.map