Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
Merge 1c439ab into a925931
Browse files Browse the repository at this point in the history
  • Loading branch information
vhf committed Mar 1, 2021
2 parents a925931 + 1c439ab commit 4b06fa1
Show file tree
Hide file tree
Showing 13 changed files with 248 additions and 5 deletions.
35 changes: 32 additions & 3 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ const parser = require('./lib/parser')
const { version } = require(path.join(__dirname, 'package.json'))
const ChecksCollection = require('./lib/checksCollection.js')
const { removeFilePart } = require('./lib/utils')
const validate = require('./lib/manifest')

const program = new Command()
program.version(version)

async function main (file, options) {
async function validatePipeline (file, options) {
const pipelineFile = path.resolve(file)
const pipelineDir = removeFilePart(pipelineFile)
const checks = new ChecksCollection()
Expand Down Expand Up @@ -43,14 +44,37 @@ async function main (file, options) {
}
}

async function validateManifest (file, options) {
const checks = new ChecksCollection()
try {
await validate({ file, checks })
}
catch (err) {
if (options.debug) {
console.error(err)
}
}

checks.print(options.levels)

if (!process.stdout.isTTY) {
console.log(checks.filterToJSON(options.levels))
}

if (checks.countIssues(options.strict)) {
process.exit(-1)
}
}

program
.arguments('<pipelineFile>')
.option('-d, --debug', 'Shows debug information', false)
.option('-m, --manifest', 'Validate a manifest.ttl instead of a pipeline', false)
.option('-p, --pipeline <pipelineIRI>', 'Pipeline IRI', null)
.option('-q, --quiet', 'Report errors only', false)
.option('-s, --strict', 'Produce an error exit status on warnings', false)
.option('-v, --verbose', 'Include successful validation checks in output', false)
.action((pipelineFile, options) => {
.action(async (file, options) => {
options.levels = ['error']
if (!options.quiet) {
options.levels.push('warning')
Expand All @@ -61,6 +85,11 @@ program
if (options.strict) {
options.verbose = true
}
main(pipelineFile, options)
if (!options.manifest) {
validatePipeline(file, options)
}
else {
await validateManifest(file, options)
}
})
program.parse()
46 changes: 46 additions & 0 deletions lib/manifest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const path = require('path')
const parser = require('./parser')
const namespace = require('@rdfjs/namespace')
const { removeFilePart } = require('./utils')
const iriResolve = require('rdf-loader-code/lib/iriResolve')
const validators = require('./validators')

const ns = {
schema: namespace('http://schema.org/'),
rdf: namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#'),
p: namespace('https://pipeline.described.at/'),
code: namespace('https://code.described.at/')
}

async function validate ({ file, checks }) {
const manifestPath = path.resolve(file)
const manifestDir = removeFilePart(manifestPath)
const manifestDirPart = manifestDir.substr(manifestDir.lastIndexOf('/') + 1)

const manifestGraph = await parser.readGraph(manifestPath, checks)
const operations = manifestGraph.has(ns.rdf.type, ns.p.Operation)
.map(operation => {
const implementedBy = operation.out(ns.code.implementedBy)
const codeLink = implementedBy.out(ns.code.link)
const identifier = codeLink.term

const { filename, method } = iriResolve(identifier.value)
return { operation: operation.term.value, filename, method }
})
.map(({ operation, filename, method }) => {
filename = manifestDir + filename.replace(manifestDirPart, '')
method = method || 'default'
return { operation, filename, method }
})

await operations.reduce(async (promise, { operation: op, filename, method }) => {
await promise
const fileIssue = await validators.operationIsImportable.validate({ op, filename })
checks.addGenericCheck(fileIssue)
if (fileIssue.level !== 'error') {
checks.addGenericCheck(await validators.operationIsExported.validate({ op, filename, method }))
}
}, Promise.resolve())
}

module.exports = validate
Empty file removed lib/manifest.ttl
Empty file.
8 changes: 6 additions & 2 deletions lib/validators/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ const dependency = require('./dependency')
const firstOperationIsReadable = require('./firstOperationIsReadable')
const operation = require('./operation')
const operationHasOperationProperty = require('./operationHasOperationProperty')
const operationIsExported = require('./operationIsExported')
const operationIsImportable = require('./operationIsImportable')
const operationPropertiesExist = require('./operationPropertiesExist')
const pipelinePropertiesExist = require('./pipelinePropertiesExist')
const pipelinePropertiesMatchFirstFlex = require('./pipelinePropertiesMatchFirstFlex')
const pipelinePropertiesMatchLastFlex = require('./pipelinePropertiesMatchLastFlex')
const pipelinePropertiesMatchFirstStrict = require('./pipelinePropertiesMatchFirstStrict')
const pipelinePropertiesMatchLastFlex = require('./pipelinePropertiesMatchLastFlex')
const pipelinePropertiesMatchLastStrict = require('./pipelinePropertiesMatchLastStrict')
const previousOperationHasMetadata = require('./previousOperationHasMetadata')
const readableBeforeWritable = require('./readableBeforeWritable')
Expand All @@ -21,11 +23,13 @@ module.exports = {
firstOperationIsReadable,
operation,
operationHasOperationProperty,
operationIsExported,
operationIsImportable,
operationPropertiesExist,
pipelinePropertiesExist,
pipelinePropertiesMatchFirstFlex,
pipelinePropertiesMatchLastFlex,
pipelinePropertiesMatchFirstStrict,
pipelinePropertiesMatchLastFlex,
pipelinePropertiesMatchLastStrict,
previousOperationHasMetadata,
readableBeforeWritable,
Expand Down
35 changes: 35 additions & 0 deletions lib/validators/operationIsExported.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const { template } = require('../utils')
const Issue = require('../issue')

const operationIsExported = {
ruleId: 15,
ruleDescription: 'Operation defined in manifest has a corresponding export',
messageSuccessTemplate: template`File ${'filename'} exports '${'method'}' for operation ${'op'}`,
messageFailureTemplate: template`File ${'filename'} does not export ${'method'} for operation ${'op'}`,
async validate ({ op, filename, method }) {
let issue
try {
const imported = await import(filename)

if (typeof imported[method] !== 'function') {
issue = Issue.error({ id: this.ruleId, templateData: { op, filename, method } })
}
else {
issue = Issue.info({ id: this.ruleId, templateData: { op, filename, method } })
}
}
catch (err) {
}

return issue
},
describeRule () {
return {
ruleId: this.ruleId,
ruleDescription: this.ruleDescription,
messageSuccess: this.messageSuccessTemplate(),
messageFailure: this.messageFailureTemplate()
}
}
}
module.exports = operationIsExported
29 changes: 29 additions & 0 deletions lib/validators/operationIsImportable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const { template } = require('../utils')
const Issue = require('../issue')

const operationIsImportable = {
ruleId: 14,
ruleDescription: 'Operation defined in manifest can be imported',
messageSuccessTemplate: template`${'filename'} can be imported for operation ${'op'}`,
messageFailureTemplate: template`Cannot import ${'filename'} for operation ${'op'}`,
async validate ({ op, filename }) {
let issue = Issue.error({ id: this.ruleId, templateData: { op, filename } })

try {
await import(filename)
issue = Issue.info({ id: this.ruleId, templateData: { op, filename } })
}
catch (err) { }

return issue
},
describeRule () {
return {
ruleId: this.ruleId,
ruleDescription: this.ruleDescription,
messageSuccess: this.messageSuccessTemplate(),
messageFailure: this.messageFailureTemplate()
}
}
}
module.exports = operationIsImportable
12 changes: 12 additions & 0 deletions rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@
"messageSuccess": "Validated operation ${operation}: a ReadableObjectMode operation must be followed by a WritableObjectMode operation",
"messageFailure": "Invalid operation ${operation}: next operation is not WritableObjectMode"
},
{
"ruleId": 14,
"ruleDescription": "Operation defined in manifest can be imported",
"messageSuccess": "${filename} can be imported for operation ${op}",
"messageFailure": "Cannot import ${filename} for operation ${op}"
},
{
"ruleId": 15,
"ruleDescription": "Operation defined in manifest has a corresponding export",
"messageSuccess": "File ${filename} exports '${method}' for operation ${op}",
"messageFailure": "File ${filename} does not export ${method} for operation ${op}"
},
{
"ruleId": 51,
"ruleDescription": "Pipeline should have the same type if its first stream is Writable(ObjectMode)",
Expand Down
14 changes: 14 additions & 0 deletions test/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,18 @@ describe('barnard59-validate', function () {
const verboseInfos = JSON.parse(verbose.stdout).filter(issue => issue.level === 'info').length
assert.ok(verboseInfos > 0)
})

it('should validate manifests', () => {
const exec = chaiExec('-m ./test/fixtures/manifest-good.ttl')
const infos = JSON.parse(exec.stdout).filter(issue => issue.level === 'info').length
assert.strictEqual(infos, 0)

const verbose = chaiExec('-m ./test/fixtures/manifest-good.ttl -v')
const verboseInfos = JSON.parse(verbose.stdout).filter(issue => issue.level === 'info').length
assert.ok(verboseInfos > 0)

const error = chaiExec('-m ./test/fixtures/manifest-bad.ttl')
const errors = JSON.parse(error.stdout).filter(issue => issue.level === 'error').length
assert.strictEqual(errors, 2)
})
})
3 changes: 3 additions & 0 deletions test/fixtures/defaultexport.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function defaultexport () {

}
18 changes: 18 additions & 0 deletions test/fixtures/manifest-bad.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@base <http://barnard59.zazuko.com/operations/ftp/> .
@prefix code: <https://code.described.at/> .
@prefix p: <https://pipeline.described.at/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<a2> a p:Operation, p:ReadableObjectMode;
rdfs:label "Exported with a named export";
rdfs:comment "but imported with default import";
code:implementedBy [ a code:EcmaScript;
code:link <node:fixtures/namedexport.mjs>
].

<a4> a p:Operation, p:Writable, p:Readable;
rdfs:label "Exported with a default export";
rdfs:comment "but imported with a named import";
code:implementedBy [ a code:EcmaScript;
code:link <node:fixtures/defaultexport.mjs#namedimport>
].
18 changes: 18 additions & 0 deletions test/fixtures/manifest-good.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@base <http://barnard59.zazuko.com/operations/ftp/> .
@prefix code: <https://code.described.at/> .
@prefix p: <https://pipeline.described.at/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<a1> a p:Operation, p:ReadableObjectMode;
rdfs:label "Exported with a named export";
rdfs:comment "and imported with a named import";
code:implementedBy [ a code:EcmaScript;
code:link <node:fixtures/namedexport.mjs#myexport>
].

<a3> a p:Operation, p:Writable, p:Readable;
rdfs:label "Exported with a default export";
rdfs:comment "and imported with a default import";
code:implementedBy [ a code:EcmaScript;
code:link <node:fixtures/defaultexport.mjs>
].
3 changes: 3 additions & 0 deletions test/fixtures/namedexport.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function myexport () {

}
32 changes: 32 additions & 0 deletions test/manifest.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const { describe, it } = require('mocha')
const path = require('path')
const assert = require('assert')
const validateManifest = require('../lib/manifest')
const ChecksCollection = require('../lib/checksCollection.js')

describe('manifest', () => {
it('finds import errors', async () => {
const checks = new ChecksCollection()
const file = path.join(__dirname, './fixtures/manifest.ttl')
await validateManifest({ file, checks })
assert.ok(checks.generic.filter((issue) => issue.level === 'error').length === 1)
})

it('finds matching imports/exports', async () => {
const checks = new ChecksCollection()
const file = path.join(__dirname, './fixtures/manifest-good.ttl')
await validateManifest({ file, checks })
assert.ok(checks.generic.filter((issue) => issue.level === 'error').length === 0)
})

it('reports faulty imports/exports', async () => {
const checks = new ChecksCollection()
const file = path.join(__dirname, './fixtures/manifest-bad.ttl')
await validateManifest({ file, checks })
const errors = checks.generic.filter(issue => issue.level === 'error')
assert.ok(errors.length === 2)
const [error1, error2] = errors
assert(error1.message.includes('does not export default'))
assert(error2.message.includes('does not export namedimport'))
})
})

0 comments on commit 4b06fa1

Please sign in to comment.