@accordproject/concerto-linter
Version:
Concerto Linter using Spectral rulesets
135 lines • 6.41 kB
JavaScript
;
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.lintModel = lintModel;
const spectral_core_1 = require("@stoplight/spectral-core");
const spectral_parsers_1 = require("@stoplight/spectral-parsers");
const config_loader_1 = require("./config-loader");
const getRuleset_1 = require("@stoplight/spectral-cli/dist/services/linter/utils/getRuleset");
const concerto_linter_default_ruleset_1 = __importDefault(require("@accordproject/concerto-linter-default-ruleset"));
const concerto_cto_1 = require("@accordproject/concerto-cto");
/**
* Converts Concerto model to JSON AST representation
* @param {string | object} model - Concerto model as string or parsed object
* @returns {string} JSON string of the AST
* @throws {Error} For invalid model inputs
*/
function convertToJsonAST(model) {
try {
if (typeof model === 'string') {
const modelFile = concerto_cto_1.Parser.parseModels([model]);
return JSON.stringify(modelFile);
}
return JSON.stringify(model);
}
catch (error) {
throw new Error(`Model conversion failed: ${error instanceof Error ? error.message : error}`);
}
}
/**
* Loads Spectral ruleset based on configuration options
* @param {string} [ruleset] - Custom ruleset path or 'default'
* @returns {Promise<Ruleset | RulesetDefinition>} Loaded ruleset
*/
async function loadRuleset(ruleset) {
try {
if (typeof ruleset === 'string' && ruleset.toLowerCase() === 'default') {
return concerto_linter_default_ruleset_1.default;
}
const rulesetPath = await (0, config_loader_1.resolveRulesetPath)(ruleset);
return rulesetPath ? await (0, getRuleset_1.getRuleset)(rulesetPath) : concerto_linter_default_ruleset_1.default;
}
catch (error) {
throw new Error(`Ruleset loading failed: ${error instanceof Error ? error.message : error}`);
}
}
/**
* Formats Spectral linting results by mapping them to a standardized lint result structure,
* extracting namespaces from the provided JSON AST, and filtering out results based on excluded namespaces.
*
* @param spectralResults - An array of Spectral rule results to be formatted.
* @param jsonAST - A JSON string representing the AST, used to extract model namespaces.
* @param excludeNamespaces - A string or array of strings specifying namespace patterns to exclude from the results.
* Patterns ending with `.*` will match any namespace starting with the given prefix.
* Defaults to `['concerto.*', 'org.accord.*']`.
* @returns An array of formatted lint results, excluding those matching the specified namespaces.
*/
function formatResults(spectralResults, jsonAST, excludeNamespaces = ['concerto.*', 'org.accordproject.*']) {
try {
const ast = JSON.parse(jsonAST);
const severityMap = {
0: 'error',
1: 'warning',
2: 'info',
3: 'hint',
};
const results = spectralResults.map(r => {
let namespace = 'unknown';
if (Array.isArray(r.path) && r.path.length >= 2 && r.path[0] === 'models') {
const modelIndex = r.path[1];
const modelEntry = ast.models?.[modelIndex];
if (modelEntry && modelEntry.namespace) {
namespace = modelEntry.namespace;
}
}
return {
code: r.code,
message: r.message,
path: r.path,
severity: severityMap[r.severity],
namespace: namespace,
};
});
const exclusionPatterns = Array.isArray(excludeNamespaces) ? excludeNamespaces : [excludeNamespaces];
return results.filter(result => {
return !exclusionPatterns.some(pattern => {
if (pattern.endsWith('.*')) {
return result.namespace.startsWith(pattern.slice(0, pattern.length - 2));
}
return result.namespace === pattern;
});
});
}
catch (error) {
throw new Error(`Formatting lint results failed: ${error instanceof Error ? error.message : error}`);
}
}
/**
* Lints Concerto models using Spectral and Concerto rules.
* @param {string | object} model - The Concerto model to lint, either as a CTO string or a parsed AST object. Note: No external dependency resolution is performed.
* @param {options} [config] - Configuration options for customizing the linting process.
* @param {string} [config.ruleset] - Path to a custom Spectral ruleset file or 'default' to use the built-in ruleset.
* @param {string | string[]} [config.excludeNamespaces] - One or more namespaces to exclude from linting results (defaults to 'concerto.*' and 'org.accord.*').
* @returns {Promise<lintResult[]>} Promise resolving to an array of formatted linting results as a JSON object.
* @throws {Error} Throws an error if linting or model conversion fails.
*/
async function lintModel(model, config) {
try {
const jsonAST = convertToJsonAST(model);
const ruleset = await loadRuleset(config?.ruleset);
const spectral = new spectral_core_1.Spectral();
spectral.setRuleset(ruleset);
const document = new spectral_core_1.Document(jsonAST, spectral_parsers_1.Json);
const spectralResults = await spectral.run(document);
return formatResults(spectralResults, jsonAST, config?.excludeNamespaces);
}
catch (error) {
throw new Error(`Linting process failed: ${error instanceof Error ? error.message : error}`);
}
}
//# sourceMappingURL=index.js.map