Comparing version 0.2.8 to 1.0.0
@@ -1,390 +0,15 @@ | ||
// Generated by CoffeeScript 1.7.1 | ||
var Drafter, EXPAND_TYPES, GENERATE_BODY, GENERATE_SCHEMA, async, boutique, deepEqual, deepcopy, fs, gatherPayloads, generateBody, generateSchema, options, protagonist, | ||
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; | ||
try { | ||
var protagonist = require('protagonist'); | ||
protagonist = require('protagonist'); | ||
boutique = require('boutique'); | ||
options = require('./options'); | ||
fs = require('fs'); | ||
async = require('async'); | ||
deepcopy = require('deepcopy'); | ||
deepEqual = require('deep-equal'); | ||
GENERATE_BODY = true; | ||
GENERATE_SCHEMA = true; | ||
EXPAND_TYPES = true; | ||
gatherPayloads = function(result) { | ||
var action, actionElement, attributes, element, example, payloads, request, resolvedAttributes, response, subElement, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _len6, _m, _n, _o, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6; | ||
payloads = []; | ||
_ref = result.ast.content; | ||
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
element = _ref[_i]; | ||
if (element.element === 'category') { | ||
_ref1 = element.content; | ||
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { | ||
subElement = _ref1[_j]; | ||
if (subElement.element === 'resource') { | ||
_ref2 = subElement.actions; | ||
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { | ||
action = _ref2[_k]; | ||
attributes = null; | ||
resolvedAttributes = null; | ||
_ref3 = action.content; | ||
for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { | ||
actionElement = _ref3[_l]; | ||
if (actionElement.element === 'dataStructure') { | ||
attributes = actionElement; | ||
} | ||
if (actionElement.element === 'resolvedDataStructure') { | ||
resolvedAttributes = actionElement; | ||
} | ||
} | ||
if (resolvedAttributes == null) { | ||
resolvedAttributes = attributes; | ||
} | ||
_ref4 = action.examples; | ||
for (_m = 0, _len4 = _ref4.length; _m < _len4; _m++) { | ||
example = _ref4[_m]; | ||
_ref5 = example.requests; | ||
for (_n = 0, _len5 = _ref5.length; _n < _len5; _n++) { | ||
request = _ref5[_n]; | ||
payloads.push({ | ||
payload: request, | ||
actionAttributes: resolvedAttributes | ||
}); | ||
} | ||
_ref6 = example.responses; | ||
for (_o = 0, _len6 = _ref6.length; _o < _len6; _o++) { | ||
response = _ref6[_o]; | ||
payloads.push({ | ||
payload: response, | ||
actionAttributes: resolvedAttributes | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return payloads; | ||
}; | ||
generateBody = function(payload, attributes, contentType, callback) { | ||
if (GENERATE_BODY !== true) { | ||
return callback(null); | ||
} | ||
if ((attributes == null) || (contentType == null) || payload.body) { | ||
return callback(null); | ||
} | ||
return boutique.represent({ | ||
ast: attributes, | ||
contentType: contentType | ||
}, function(error, body) { | ||
var resolved; | ||
if ((error == null) && body) { | ||
resolved = { | ||
element: 'resolvedAsset', | ||
attributes: { | ||
role: 'bodyExample' | ||
}, | ||
content: body | ||
}; | ||
payload.content.push(resolved); | ||
payload.body = body; | ||
} | ||
return callback(null); | ||
}); | ||
}; | ||
generateSchema = function(payload, attributes, contentType, callback) { | ||
if (GENERATE_SCHEMA !== true) { | ||
return callback(null); | ||
} | ||
if ((attributes == null) || payload.schema || contentType.indexOf('json') === -1) { | ||
return callback(null); | ||
} | ||
return boutique.represent({ | ||
ast: attributes, | ||
contentType: 'application/schema+json' | ||
}, function(error, body) { | ||
var resolved; | ||
if ((error == null) && body) { | ||
resolved = { | ||
element: 'resolvedAsset', | ||
attributes: { | ||
role: 'bodySchema' | ||
}, | ||
content: body | ||
}; | ||
payload.content.push(resolved); | ||
payload.schema = body; | ||
} | ||
return callback(null); | ||
}); | ||
}; | ||
Drafter = (function() { | ||
Drafter.dataStructures = {}; | ||
Drafter.origDataStructures = {}; | ||
Drafter.appendResolved = {}; | ||
Drafter.defaultConfig = { | ||
requireBlueprintName: false, | ||
exportSourcemap: false | ||
module.exports = { | ||
parse: protagonist.parse, | ||
parseSync: protagonist.parseSync, | ||
}; | ||
} catch (error) { | ||
var drafterjs = require('drafter.js'); | ||
function Drafter(config) { | ||
this.config = config; | ||
if (!this.config) { | ||
this.config = Drafter.defaultConfig; | ||
} | ||
} | ||
Drafter.prototype.makeFromPath = function(blueprintPath, callback) { | ||
return fs.readFile(blueprintPath, 'utf8', (function(_this) { | ||
return function(error, source) { | ||
if (error) { | ||
return callback(error); | ||
} | ||
return _this.make(source, callback); | ||
}; | ||
})(this)); | ||
module.exports = { | ||
parse: drafterjs.parse, | ||
parseSync: drafterjs.parseSync, | ||
}; | ||
Drafter.prototype.make = function(source, callback) { | ||
return protagonist.parse(source, this.config, (function(_this) { | ||
return function(error, result) { | ||
var payloads, rule, ruleList, rules; | ||
if (error) { | ||
return callback(error); | ||
} | ||
if (EXPAND_TYPES !== true) { | ||
return callback(null, result); | ||
} | ||
ruleList = ['mson-inheritance', 'mson-mixin', 'mson-member-type-name']; | ||
rules = (function() { | ||
var _i, _len, _results; | ||
_results = []; | ||
for (_i = 0, _len = ruleList.length; _i < _len; _i++) { | ||
rule = ruleList[_i]; | ||
_results.push(require('./rules/' + rule)); | ||
} | ||
return _results; | ||
})(); | ||
_this.dataStructures = {}; | ||
_this.origDataStructures = {}; | ||
_this.appendResolved = {}; | ||
delete result.ast.resourceGroups; | ||
_this.expandNode(result.ast, rules, 'blueprint'); | ||
payloads = gatherPayloads(result); | ||
return async.each(payloads, _this.resolvePayload, function(error) { | ||
_this.reconstructResourceGroups(result.ast); | ||
return callback(error || null, result); | ||
}); | ||
}; | ||
})(this)); | ||
}; | ||
Drafter.prototype.resolvePayload = function(_arg, callback) { | ||
var actionAttributes, attributes, contentType, element, header, payload, resolvedAttributes, _i, _j, _len, _len1, _ref, _ref1; | ||
payload = _arg.payload, actionAttributes = _arg.actionAttributes; | ||
attributes = null; | ||
resolvedAttributes = null; | ||
contentType = ''; | ||
_ref = payload.headers; | ||
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
header = _ref[_i]; | ||
if (header.name === 'Content-Type') { | ||
contentType = header.value; | ||
} | ||
} | ||
_ref1 = payload.content; | ||
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { | ||
element = _ref1[_j]; | ||
if (element.element === 'dataStructure') { | ||
attributes = element; | ||
} | ||
if (element.element === 'resolvedDataStructure') { | ||
resolvedAttributes = element; | ||
} | ||
} | ||
if (resolvedAttributes == null) { | ||
resolvedAttributes = attributes; | ||
} | ||
if (resolvedAttributes == null) { | ||
resolvedAttributes = actionAttributes; | ||
} | ||
return generateBody(payload, resolvedAttributes, contentType, function(error) { | ||
if (error) { | ||
return callback(error); | ||
} | ||
return generateSchema(payload, resolvedAttributes, contentType, function(error) { | ||
return callback(error || null); | ||
}); | ||
}); | ||
}; | ||
Drafter.prototype.expandNode = function(node, rules, elementType, parentContent, parentElementType) { | ||
var action, dataStructure, element, example, name, newNode, request, resourceSubElement, response, rule, subElement, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _len6, _len7, _len8, _len9, _m, _n, _o, _p, _q, _r, _ref, _ref1, _ref10, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _results; | ||
if (elementType == null) { | ||
elementType = node.element; | ||
} | ||
if (elementType === 'blueprint') { | ||
_ref = node.content; | ||
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
element = _ref[_i]; | ||
if (element.element === 'category') { | ||
_ref1 = element.content; | ||
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { | ||
subElement = _ref1[_j]; | ||
switch (subElement.element) { | ||
case 'dataStructure': | ||
if (typeof subElement.name === 'object' && ((_ref2 = subElement.name) != null ? _ref2.literal : void 0)) { | ||
this.dataStructures[subElement.name.literal] = deepcopy(subElement); | ||
this.origDataStructures[subElement.name.literal] = subElement; | ||
this.appendResolved[subElement.name.literal] = element.content; | ||
} | ||
break; | ||
case 'resource': | ||
_ref3 = subElement.content; | ||
for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) { | ||
resourceSubElement = _ref3[_k]; | ||
if (resourceSubElement.element === 'dataStructure' && typeof resourceSubElement.name === 'object' && ((_ref4 = resourceSubElement.name) != null ? _ref4.literal : void 0)) { | ||
this.dataStructures[resourceSubElement.name.literal] = deepcopy(resourceSubElement); | ||
this.origDataStructures[resourceSubElement.name.literal] = resourceSubElement; | ||
this.appendResolved[resourceSubElement.name.literal] = subElement.content; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
for (_l = 0, _len3 = rules.length; _l < _len3; _l++) { | ||
rule = rules[_l]; | ||
if (rule.init) { | ||
rule.init.call(rule, this.dataStructures); | ||
} | ||
} | ||
_ref5 = this.dataStructures; | ||
for (name in _ref5) { | ||
dataStructure = _ref5[name]; | ||
if (!deepEqual(dataStructure, this.origDataStructures[name])) { | ||
dataStructure.element = 'resolvedDataStructure'; | ||
this.appendResolved[name].push(dataStructure); | ||
} | ||
} | ||
} | ||
if (elementType === 'resolvedDataStructure') { | ||
return; | ||
} | ||
if (elementType === 'dataStructure') { | ||
newNode = deepcopy(node); | ||
} else { | ||
newNode = node; | ||
} | ||
for (_m = 0, _len4 = rules.length; _m < _len4; _m++) { | ||
rule = rules[_m]; | ||
if (__indexOf.call(Object.keys(rule), elementType) >= 0) { | ||
rule[elementType].call(rule, newNode); | ||
} | ||
} | ||
if ((parentElementType !== 'resource' && parentElementType !== 'blueprint') && elementType === 'dataStructure' && !deepEqual(node, newNode)) { | ||
newNode.element = 'resolvedDataStructure'; | ||
parentContent.push(newNode); | ||
} | ||
switch (elementType) { | ||
case 'resource': | ||
_ref6 = node.actions; | ||
for (_n = 0, _len5 = _ref6.length; _n < _len5; _n++) { | ||
action = _ref6[_n]; | ||
this.expandNode(action, rules, 'action'); | ||
} | ||
break; | ||
case 'action': | ||
_ref7 = node.examples; | ||
for (_o = 0, _len6 = _ref7.length; _o < _len6; _o++) { | ||
example = _ref7[_o]; | ||
this.expandNode(example, rules, 'transactionExample'); | ||
} | ||
break; | ||
case 'transactionExample': | ||
_ref8 = node.requests; | ||
for (_p = 0, _len7 = _ref8.length; _p < _len7; _p++) { | ||
request = _ref8[_p]; | ||
this.expandNode(request, rules, 'payload'); | ||
} | ||
_ref9 = node.responses; | ||
for (_q = 0, _len8 = _ref9.length; _q < _len8; _q++) { | ||
response = _ref9[_q]; | ||
this.expandNode(response, rules, 'payload'); | ||
} | ||
} | ||
if (node.content && Array.isArray(node.content)) { | ||
_ref10 = node.content; | ||
_results = []; | ||
for (_r = 0, _len9 = _ref10.length; _r < _len9; _r++) { | ||
element = _ref10[_r]; | ||
_results.push(this.expandNode(element, rules, null, node.content, elementType)); | ||
} | ||
return _results; | ||
} | ||
}; | ||
Drafter.prototype.reconstructResourceGroups = function(ast) { | ||
var description, element, name, resources, subElement, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3, _ref4, _results; | ||
ast.resourceGroups = []; | ||
_ref = ast.content; | ||
_results = []; | ||
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
element = _ref[_i]; | ||
if (element.element === 'category') { | ||
resources = []; | ||
_ref1 = element.content; | ||
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { | ||
subElement = _ref1[_j]; | ||
if (subElement.element === 'resource') { | ||
resources.push(subElement); | ||
} | ||
} | ||
description = null; | ||
name = (_ref2 = element.attributes) != null ? _ref2.name : void 0; | ||
if (((_ref3 = element.content[0]) != null ? _ref3.element : void 0) === 'copy') { | ||
if (((_ref4 = element.content[0]) != null ? _ref4.element : void 0) === 'copy') { | ||
description = element.content[0].content; | ||
} | ||
} | ||
if (resources.length || description || name) { | ||
_results.push(ast.resourceGroups.push({ | ||
name: name || '', | ||
description: description || '', | ||
resources: resources | ||
})); | ||
} else { | ||
_results.push(void 0); | ||
} | ||
} else { | ||
_results.push(void 0); | ||
} | ||
} | ||
return _results; | ||
}; | ||
return Drafter; | ||
})(); | ||
module.exports = Drafter; | ||
module.exports.options = options; | ||
} |
{ | ||
"name": "drafter", | ||
"version": "0.2.8", | ||
"author": "Apiary.io <support@apiary.io>", | ||
"description": "Snow Crash parser harness", | ||
"license": "MIT", | ||
"version": "1.0.0", | ||
"description": "Node API Blueprint Parser", | ||
"main": "lib/drafter.js", | ||
"bin": { | ||
"drafter": "bin/drafter" | ||
}, | ||
"repository": "apiaryio/drafter.js", | ||
"engines": { | ||
"node": ">= 0.10.x" | ||
"node": ">= 0.12" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/apiaryio/drafter-npm" | ||
}, | ||
"author": "Apiary Czech Republic, s.r.o. <support@apiary.io>", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/apiaryio/drafter-npm/issues" | ||
}, | ||
"homepage": "https://github.com/apiaryio/drafter-npm", | ||
"scripts": { | ||
"test": "scripts/test", | ||
"prepublish": "scripts/build" | ||
"test": "mocha" | ||
}, | ||
"dependencies": { | ||
"async": "~0.9.0", | ||
"boutique": "~0.1.7", | ||
"deep-equal": "1.0.x", | ||
"deepcopy": "0.4.x", | ||
"protagonist": "~0.20.1", | ||
"yargs": "~1.3.3" | ||
"drafter.js": "^2.4.3" | ||
}, | ||
"optionalDependencies": { | ||
"protagonist": "^1.4.1" | ||
}, | ||
"devDependencies": { | ||
"coffee-script": "~1.7.1", | ||
"chai": "~1.10.0", | ||
"mocha": "~2.0.1" | ||
"mocha": "~1.17.1", | ||
"chai": "~1.9.0" | ||
} | ||
} |
129
README.md
@@ -1,104 +0,81 @@ | ||
# Drafter | ||
![logo](https://raw.github.com/apiaryio/api-blueprint/master/assets/logo_apiblueprint.png) | ||
[![Circle CI](https://circleci.com/gh/apiaryio/drafter.js.svg?style=svg&circle-token=f4b9c3fc34979e81d36c9d15e576e23f62e1e913)](https://circleci.com/gh/apiaryio/drafter.js) | ||
# Drafter NPM Package [![Build Status](https://travis-ci.org/apiaryio/drafter-npm.svg?branch=master)](https://travis-ci.org/apiaryio/drafter-npm) | ||
Snow Crash parser harness. | ||
The Drafter NPM package is an API Blueprint parser for Node. This package is a | ||
wrapper around the underlying C++ parser | ||
[Drafter](https://github.com/apiaryio/drafter). Drafter NPM optionally depends | ||
on the C++ binding to Drafter | ||
[Protagonist](https://github.com/apiaryio/protagonist). If for any reason | ||
Protagonist is not installable, this package will fallback to using the slower, | ||
pure JavaScript version of Drafter, | ||
[drafter.js](https://github.com/apiaryio/drafter.js). | ||
## Introduction | ||
Drafter takes an API blueprint on its input, parses, and then processes the AST to exposes the [Parse Result][] for further use. Drafter expands MSON data structures from the AST and generates JSON representations and JSON Schema representation of MSON structures where they are not found in the original AST. | ||
## Installation | ||
Node.js v0.10 is required. | ||
Drafter can be installed from NPM. If you want to use Drafter from a web | ||
browser, check out [drafter.js](https://github.com/apiaryio/drafter.js). | ||
```shell | ||
$ npm install -g drafter | ||
$ npm install drafter | ||
``` | ||
## Getting Started | ||
## Usage | ||
### Library | ||
```js | ||
var Drafter = require('drafter'); | ||
var blueprint = '# GET /message\n' + | ||
'+ Response 200\n' + | ||
'\n' + | ||
' Hello World!\n' | ||
var drafter = require('drafter'); | ||
``` | ||
var drafter = new Drafter; | ||
drafter.make(blueprint, function(error, result) { | ||
if (error) { | ||
console.log(error); | ||
return; | ||
} | ||
Once you've included drafter, you can parse an API Blueprint asynchronously: | ||
console.log(JSON.stringify(result, null, 2)); | ||
```js | ||
var options = { | ||
generateSourcemap: true, | ||
}; | ||
drafter.parse('# API Blueprint...', options, function(err, result) { | ||
if (err) { | ||
console.log(err); | ||
} else { | ||
console.log(result); | ||
} | ||
}); | ||
``` | ||
### CLI Tool | ||
Alternatively, you can use Drafter synchronously: | ||
```shell | ||
$ cat << 'EOF' > blueprint.apib | ||
# GET /message | ||
+ Response 200 | ||
Hello World! | ||
EOF | ||
$ drafter blueprint.apib | ||
```js | ||
try { | ||
var result = drafter.parse('# API Blueprint...', options); | ||
console.log(result); | ||
} catch (err) { | ||
console.log(err); | ||
} | ||
``` | ||
## Resolved Named Types | ||
### Parsing Options | ||
The three rules for when MSON AST is expanded are: | ||
Options can be passed to the parser as an optional second argument to both the | ||
asynchronous and synchronous interfaces: | ||
* If a named type is a sub-type of another named type | ||
* If a named types includes a mixin | ||
* If a value member or property member is referencing a named type | ||
```js | ||
var options = { | ||
generateSourceMap: true | ||
} | ||
The expanded data structures are added to the array which has the original data structures with their element name set to `resolvedDataStructure`. | ||
## Resolved Assets | ||
The resolved assets for a *payload body example* and *payload body schema* are added to the array in the `content` key of the **Payload Object** with their element name set to `resolvedAsset` and `role` in `attributes` set as `bodyExample` and `bodySchema` respectively. | ||
A sample part of payload object is given below | ||
```json | ||
{ | ||
"content": [ | ||
{ | ||
"element": "resolvedAsset", | ||
"attributes": { | ||
"role": "bodyExample" | ||
}, | ||
"content": "{\"id\":\"250FF\",\"percent_off\":25,\"redeem_by\":null}" | ||
}, | ||
{ | ||
"element": "resolvedAsset", | ||
"attributes": { | ||
"role": "bodySchema" | ||
}, | ||
"content": "{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"},\"percent_off\":{\"type\":\"number\"},\"redeem_by\":{\"type\":\"number\",\"description\":\"Date after which the coupon can no longer be redeemed\"}},\"$schema\":\"http://json-schema.org/draft-04/schema#\"}" | ||
} | ||
] | ||
} | ||
drafter.parse('# My API', options, callback); | ||
``` | ||
## Testing | ||
The available options are: | ||
Inside the drafter repository you can execute the following to run the test suite: | ||
Name | Description | ||
---------------------- | ---------------------------------------------------------- | ||
`requireBlueprintName` | Require parsed blueprints have a title (default: false) | ||
`generateSourceMap` | Enable sourcemap generation (default: false) | ||
`type` | Set the output structure type as either `ast` or `refract` (default: `refract`) | ||
```bash | ||
$ npm install | ||
$ npm test | ||
``` | ||
**NOTE**: *The `ast` option is deprecated in favour of `refract`.* | ||
### Contribute | ||
Fork & Pull Request. | ||
## License | ||
MIT License. See the [LICENSE](https://github.com/apiaryio/drafter.js/blob/master/LICENSE) file. | ||
[Boutique]: https://github.com/apiaryio/boutique.js | ||
[Parse Result]: https://github.com/apiaryio/api-blueprint-ast/blob/master/Parse%20Result.md | ||
MIT License. See the [LICENSE](LICENSE) file. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
2
2
0
0
0
6459
7
43
82
2
+ Addeddrafter.js@^2.4.3
+ Addeddrafter.js@2.6.7(transitive)
+ Addednan@2.22.0(transitive)
+ Addedprotagonist@1.6.8(transitive)
- Removedasync@~0.9.0
- Removedboutique@~0.1.7
- Removeddeep-equal@1.0.x
- Removeddeepcopy@0.4.x
- Removedprotagonist@~0.20.1
- Removedyargs@~1.3.3
- Removedasync@0.9.2(transitive)
- Removedboutique@0.1.7(transitive)
- Removeddeep-equal@1.0.1(transitive)
- Removeddeepcopy@0.4.0(transitive)
- Removedmedia-typer@0.3.0(transitive)
- Removednan@1.8.4(transitive)
- Removedprotagonist@0.20.1(transitive)
- Removedtv4@1.1.12(transitive)
- Removedyargs@1.3.3(transitive)