prettify-bru 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,9 +2,12 @@
2
2
 
3
3
  A CLI tool to [prettify and format Bruno `.bru` files](https://www.npmjs.com/package/prettify-bru).
4
4
 
5
- Uses [Prettier](https://prettier.io/) to impose a standard format on all blocks of JSON and JavaScript code across multiple [Bruno](https://www.usebruno.com/) `.bru` files in your project.
5
+ Removes junk and makes code shorter and more transferable between systems.
6
+ Imposes a standard format on all blocks of JSON and JavaScript code across multiple [Bruno](https://www.usebruno.com/) `.bru` files in your project.
6
7
 
7
- Handles blocks: `body:json`, `script:pre-request`, `script:post-response` and `tests`. All blocks are parsed using [Babel](https://babeljs.io/docs/babel-parser).
8
+ `body:json` blocks are formatted using [jsonc-parser](https://www.npmjs.com/package/jsonc-parser)
9
+
10
+ `script:pre-request`, `script:post-response` and `tests` blocks are formatted using [Prettier](https://prettier.io/) with [Babel](https://babeljs.io/docs/babel-parser) parser.
8
11
 
9
12
  ## Table of contents
10
13
 
@@ -117,7 +120,47 @@ npm prettify-bru --write --only body:json speed-tests/get-all.bru
117
120
 
118
121
  ## Config file
119
122
 
120
- You can override any of the [Prettier options](https://prettier.io/docs/options) via the `prettier` property in your `.prettifybrurc` file.
123
+ Create a `.prettifybrurc` file containing a JSON object with one or more of the following properties:
124
+
125
+ ### Agnostic File Paths
126
+
127
+ Property: `agnosticFilePaths` {boolean} (Default: `true`)
128
+
129
+ Replaces backslash folder separators in filenames with forward slashes so they work on Windows, Mac and Linux
130
+
131
+ ```
132
+ file: @file(\Images\Memes\3-Spidermen.jpg) @contentType(image/jpeg)
133
+ ```
134
+
135
+ will be changed to...
136
+
137
+ ```
138
+ file: @file(/Images/Memes/3-Spidermen.jpg) @contentType(image/jpeg)
139
+ ```
140
+
141
+ ### Shorten Getters
142
+
143
+ Property: `shortenGetters` {boolean} (Default: `true`)
144
+
145
+ Shorten code by replacing uses of getters with property references
146
+
147
+ ```javascript
148
+ expect(res.getStatus()).to.eql(200)
149
+ expect(res.getBody().name).to.eql("Dave")
150
+ ```
151
+
152
+ The above will become...
153
+
154
+ ```javascript
155
+ expect(res.status).to.eql(200)
156
+ expect(res.body.name).to.eql("Dave")
157
+ ```
158
+
159
+ ### Prettier
160
+
161
+ Property: `prettier` {Object} (Default: `{}`)
162
+
163
+ The `prettier` property object can contain overrides for any of the [Prettier options](https://prettier.io/docs/options).
121
164
 
122
165
  For example, to increase the line length limit from the default 80 up to 120 characters, your file would contain:
123
166
 
@@ -129,7 +172,7 @@ For example, to increase the line length limit from the default 80 up to 120 cha
129
172
  }
130
173
  ```
131
174
 
132
- *Note: Config file is supported from version 1.6.0 and above.*
175
+ *Note: Config file is supported from version [1.6.0](CHANGELOG.md#160) and above.*
133
176
 
134
177
  ## Automatically checking PRs
135
178
 
package/lib/config.mjs CHANGED
@@ -2,6 +2,20 @@ import {readIfExists} from './files.mjs'
2
2
 
3
3
  const configFilename = '.prettifybrurc'
4
4
 
5
+ /**
6
+ * @typedef {Object} PrettifyBruConfig
7
+ * @property {boolean} agnosticFilePaths
8
+ * @property {boolean} shortenGetters
9
+ * @property {Object} prettier Prettier options
10
+ */
11
+
12
+ /** @type {PrettifyBruConfig} */
13
+ export const defaultConfig = {
14
+ agnosticFilePaths: true,
15
+ shortenGetters: true,
16
+ prettier: {},
17
+ }
18
+
5
19
  /**
6
20
  *
7
21
  * @param {Object} console
@@ -44,21 +58,26 @@ export function parseFile(console, fileContents) {
44
58
 
45
59
  let config = {}
46
60
 
47
- const supportedProperties = ['prettier']
61
+ const supportedProperties = {
62
+ agnosticFilePaths: 'a boolean',
63
+ shortenGetters: 'a boolean',
64
+ prettier: 'an object',
65
+ }
48
66
  Object.keys(fileConfig).forEach(key => {
49
- if (supportedProperties.includes(key)) {
50
- if (key === 'prettier') {
51
- const value = fileConfig[key]
52
- if (value === null || typeof value !== 'object' || Array.isArray(value)) {
53
- console.warn(
54
- `⚠️ \x1b[33mprettier is not correct type, it should be an object\x1b[0m`
55
- )
56
- return
57
- }
67
+ if (Object.hasOwn(supportedProperties, key)) {
68
+ const value = fileConfig[key]
69
+ const validType = supportedProperties[key]
70
+ const validates = validators[validType]
71
+
72
+ if (validates(value)) {
73
+ config[key] = fileConfig[key]
74
+ } else {
75
+ console.warn(
76
+ `⚠️ \x1b[33m"${key}" is not correct type, it should be ${validType}\x1b[0m`
77
+ )
58
78
  }
59
- config[key] = fileConfig[key]
60
79
  } else {
61
- console.warn(`⚠️ \x1b[33m${key} is not a supported property\x1b[0m`)
80
+ console.warn(`⚠️ \x1b[33mIgnoring unsupported property "${key}"\x1b[0m`)
62
81
  }
63
82
  })
64
83
 
@@ -66,3 +85,12 @@ export function parseFile(console, fileContents) {
66
85
 
67
86
  return config
68
87
  }
88
+
89
+ const validators = {
90
+ 'an object': value => {
91
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
92
+ },
93
+ 'a boolean': value => {
94
+ return typeof value === 'boolean'
95
+ },
96
+ }
package/lib/format.mjs CHANGED
@@ -1,4 +1,6 @@
1
1
  import prettier from 'prettier'
2
+ import {defaultConfig} from './config.mjs'
3
+ import {format as jsoncFormat, applyEdits} from 'jsonc-parser'
2
4
 
3
5
  // This Prettier config should match what the Bruno GUI implements
4
6
  const defaultPrettierConfig = {
@@ -10,14 +12,10 @@ const defaultPrettierConfig = {
10
12
  printWidth: 80,
11
13
  trailingComma: 'none',
12
14
  endOfLine: 'lf',
15
+ parser: 'babel',
13
16
  }
14
17
 
15
- const formattableBlocks = [
16
- ['body:json', 'json'],
17
- ['script:pre-request', 'babel'],
18
- ['script:post-response', 'babel'],
19
- ['tests', 'babel'],
20
- ]
18
+ const formattableBlocks = ['body:json', 'script:pre-request', 'script:post-response', 'tests']
21
19
 
22
20
  /**
23
21
  * @typedef {Object} FileOutcome
@@ -28,14 +26,26 @@ const formattableBlocks = [
28
26
  */
29
27
 
30
28
  /**
31
- * Tidies overall structure of bru lang file and uses Prettier to format blocks of JSON/JavaScript
29
+ * Tidies structure of bru lang file, makes code shorter and operating system agnostic,
30
+ * applies standard formatting to both JSON and JavaScript blocks
32
31
  *
33
32
  * @param {string} originalContents The file contents as loaded from file system
34
33
  * @param {?string} only Limit to only the block type with a name containing value
35
- * @param {Object} config
34
+ * @param {Object} configOverrides Could be whole PrettifyBruConfig or partial
36
35
  * @returns {Promise<FileOutcome>}
37
36
  */
38
- export async function format(originalContents, only = null, config = {}) {
37
+ export async function format(originalContents, only = null, configOverrides = {}) {
38
+ /** @type {import('./config.mjs').PrettifyBruConfig} */
39
+ const config = {
40
+ ...defaultConfig,
41
+ ...configOverrides,
42
+ prettier: Object.assign(
43
+ {},
44
+ defaultPrettierConfig,
45
+ Object.hasOwn(configOverrides, 'prettier') ? configOverrides.prettier : {}
46
+ ),
47
+ }
48
+
39
49
  let fileOutcome = {
40
50
  newContents: originalContents.replace(/\r\n/g, '\n'),
41
51
  blocksSearchedFor: 0,
@@ -43,23 +53,10 @@ export async function format(originalContents, only = null, config = {}) {
43
53
  errorMessages: [],
44
54
  }
45
55
 
46
- const prettierConfig = Object.assign(
47
- {},
48
- defaultPrettierConfig,
49
- Object.hasOwn(config, 'prettier') ? config.prettier : {}
50
- )
51
-
52
- let i
53
- for (i in formattableBlocks) {
54
- let [blockName, parser] = formattableBlocks[i]
56
+ for (const blockName of formattableBlocks) {
55
57
  if (only !== null && !blockName.includes(only)) continue
56
58
 
57
- const blockOutcome = await formatBlock(
58
- fileOutcome.newContents,
59
- blockName,
60
- parser,
61
- prettierConfig
62
- )
59
+ const blockOutcome = await formatBlock(fileOutcome.newContents, blockName, config)
63
60
  fileOutcome.blocksSearchedFor++
64
61
  if (blockOutcome.errorMessage !== null) {
65
62
  fileOutcome.errorMessages.push(blockOutcome.errorMessage)
@@ -69,7 +66,16 @@ export async function format(originalContents, only = null, config = {}) {
69
66
  }
70
67
  }
71
68
 
72
- if (only === null) {
69
+ formattableBlocks.forEach(blockName => {
70
+ const emptyBlockOutcome = stripEmptyBlock(fileOutcome.newContents, blockName)
71
+
72
+ if (emptyBlockOutcome.changeable) {
73
+ fileOutcome.changeable = true
74
+ fileOutcome.newContents = emptyBlockOutcome.fileContents
75
+ }
76
+ })
77
+
78
+ if (only === null && config.agnosticFilePaths) {
73
79
  const fileBodyBlockOutcome = formatFilePaths(fileOutcome.newContents)
74
80
  if (fileBodyBlockOutcome.errorMessage !== null) {
75
81
  fileOutcome.errorMessages.push(fileBodyBlockOutcome.errorMessage)
@@ -91,11 +97,10 @@ export async function format(originalContents, only = null, config = {}) {
91
97
  /**
92
98
  * @param {string} fileContents
93
99
  * @param {string} blockName
94
- * @param {string} parser
95
- * @param {Object} prettierConfig
100
+ * @param {import('./config.mjs').PrettifyBruConfig} config
96
101
  * @returns {Promise<{fileContents: string, changeable: boolean, errorMessage: ?string}>}
97
102
  */
98
- async function formatBlock(fileContents, blockName, parser, prettierConfig) {
103
+ async function formatBlock(fileContents, blockName, config) {
99
104
  let outcome = {fileContents, changeable: false, errorMessage: null}
100
105
 
101
106
  const blockBodyRegex = new RegExp('\n' + blockName + ' [{]\\n(.+?)\\n}\\n', 's')
@@ -108,27 +113,27 @@ async function formatBlock(fileContents, blockName, parser, prettierConfig) {
108
113
  // Remove 2-spaces of indentation, added due to being inside a Bru lang block
109
114
  let unindented = rawBody.replace(/^ /gm, '')
110
115
 
111
- if (blockName === 'body:json') {
112
- unindented = wrapNonStringPlaceholdersInDelimiters(unindented)
116
+ if (config.shortenGetters && ['script:post-response', 'tests'].includes(blockName)) {
117
+ unindented = shortenGetters(unindented)
113
118
  }
114
119
 
115
- let prettierFormatted
116
-
117
- try {
118
- prettierFormatted = await prettier.format(
119
- unindented,
120
- Object.assign({}, prettierConfig, {parser})
121
- )
122
- } catch (e) {
123
- outcome.errorMessage = `Prettier could not format ${blockName} because...\n${e.message}`
124
- return outcome
125
- }
120
+ let reformatted
126
121
 
127
122
  if (blockName === 'body:json') {
128
- prettierFormatted = unwrapDelimitedPlaceholders(prettierFormatted)
123
+ unindented = wrapNonStringPlaceholdersInDelimiters(unindented)
124
+ const edits = jsoncFormat(unindented, undefined, {tabSize: 2, insertSpaces: true})
125
+ reformatted = applyEdits(unindented, edits)
126
+ reformatted = unwrapDelimitedPlaceholders(reformatted)
127
+ } else {
128
+ try {
129
+ reformatted = await prettier.format(unindented, config.prettier)
130
+ } catch (e) {
131
+ outcome.errorMessage = `Prettier could not format ${blockName} because...\n${e.message}`
132
+ return outcome
133
+ }
129
134
  }
130
135
 
131
- const bodyLines = prettierFormatted.split('\n')
136
+ const bodyLines = reformatted.split('\n')
132
137
 
133
138
  // Remove leading/trailing empty lines
134
139
  while (bodyLines.length && bodyLines[0].trim() === '') bodyLines.shift()
@@ -149,6 +154,20 @@ async function formatBlock(fileContents, blockName, parser, prettierConfig) {
149
154
  return outcome
150
155
  }
151
156
 
157
+ /**
158
+ * @param {string} blockContents
159
+ * @returns {string}
160
+ */
161
+ function shortenGetters(blockContents) {
162
+ const props = ['body', 'headers', 'responseTime', 'status', 'statusText', 'url']
163
+ props.forEach(prop => {
164
+ const getter = 'get' + prop.substring(0, 1).toUpperCase() + prop.substring(1)
165
+ const getterRegex = new RegExp('(?<!\\w)res\.' + getter + '\\(\\)', 'g')
166
+ blockContents = blockContents.replaceAll(getterRegex, `res.${prop}`)
167
+ })
168
+ return blockContents
169
+ }
170
+
152
171
  /**
153
172
  * Turns Bruno variable placeholders into strings with special delimiters, effectively making it valid JSON
154
173
  *
@@ -171,7 +190,25 @@ function unwrapDelimitedPlaceholders(jsonBlock) {
171
190
 
172
191
  /**
173
192
  * @param {string} fileContents
174
- * @returns {fileContents: string, changeable: boolean, errorMessage: ?string}
193
+ * @param {string} blockName
194
+ * @returns {{fileContents: string, changeable: boolean}}
195
+ */
196
+ function stripEmptyBlock(fileContents, blockName) {
197
+ const emptyBlockRegex = new RegExp('\n' + blockName + ' [{]\\n}\\n', 's')
198
+
199
+ if (fileContents.match(emptyBlockRegex) !== null) {
200
+ return {
201
+ fileContents: fileContents.replace(emptyBlockRegex, ''),
202
+ changeable: true,
203
+ }
204
+ }
205
+
206
+ return {fileContents, changeable: false}
207
+ }
208
+
209
+ /**
210
+ * @param {string} fileContents
211
+ * @returns {{fileContents: string, changeable: boolean, errorMessage: ?string}}
175
212
  */
176
213
  function formatFilePaths(fileContents) {
177
214
  let changeable = false
@@ -192,7 +229,7 @@ function formatFilePaths(fileContents) {
192
229
 
193
230
  /**
194
231
  * @param {string} fileContents
195
- * @returns {fileContents: string, changeable: boolean}
232
+ * @returns {{fileContents: string, changeable: boolean}}
196
233
  */
197
234
  function formatOverallStructure(fileContents) {
198
235
  if (fileContents.match(/\n[}]\n[\s]+\n\w+/s) === null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prettify-bru",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Prettifies JSON and JavaScript blocks in Bruno .bru files",
5
5
  "keywords": [
6
6
  "bruno",
@@ -28,6 +28,7 @@
28
28
  ],
29
29
  "bin": "./cli.js",
30
30
  "dependencies": {
31
+ "jsonc-parser": "^3.3.1",
31
32
  "prettier": "^3.3.3",
32
33
  "yargs": "18.0.0"
33
34
  },