Skip to content

Commit

Permalink
feat(lockfile-lint-api): replace yarnpkg/lockfile with yarnpkg/parser…
Browse files Browse the repository at this point in the history
…s for better yarn3 and fearless cooperation support (#126)

BREAKING CHANGE: lockfile-lint-api uses a new implementation of parser for yarn.lock which may differ in edge-case behavior. It's potentially less strict about what is a valid yarn.lock.
  • Loading branch information
naugtur authored Jun 9, 2022
1 parent 9b0b167 commit 18c6ae0
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 13 deletions.
10 changes: 8 additions & 2 deletions packages/lockfile-lint-api/__tests__/parseLockfile.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ describe('ParseLockfile', () => {
it('instantiating a parser with invalid object throws an error', () => {
expect(() => {
new ParseLockfile()
}).toThrowError('Did not receive options for lockfile path or type')
}).toThrowError('Did not receive options for lockfile or type')
})

it('instantiating a parser with string type throws an error', () => {
expect(() => {
new ParseLockfile('/path/to/file')
}).toThrowError('Did not receive options for lockfile path or type')
}).toThrowError('Did not receive options for lockfile or type')
})

it('instantiating a parser with inadequate options object throws an error', () => {
expect(() => {
new ParseLockfile({})
}).toThrowError('Did not receive lockfile path or text')
})

it('using a parser with an invalid option type throws an error', () => {
Expand Down
20 changes: 20 additions & 0 deletions packages/lockfile-lint-api/__tests__/parseYarnLockfile.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ describe('ParseLockfile Yarn', () => {
)
})

it('providing empty content for lockfileText throws an error', () => {
const parser = new ParseLockfile({lockfileText: '\n\n', lockfileType: 'yarn'})
expect(() => {
parser.parseSync()
}).toThrowError('Unable to parse yarn lockfile ""')
})

it('providing invalid content for lockfileText throws an error', () => {
const parser = new ParseLockfile({lockfileText: '{'.repeat(9000), lockfileType: 'yarn'})
expect(() => {
parser.parseSync()
}).toThrowError('Unable to parse yarn lockfile ""')
})
it('providing garbled content for lockfileText throws an error', () => {
const parser = new ParseLockfile({lockfileText: `# yarn lockfile v1`, lockfileType: 'yarn'})
expect(() => {
parser.parseSync()
}).toThrowError('Unable to parse yarn lockfile ""')
})

it('parsing a yarn lockfile with invalid content throws an error', () => {
const mockYarnLockfilePath = path.join(__dirname, './__fixtures__/bad-yarn.lock')
const options = {
Expand Down
2 changes: 1 addition & 1 deletion packages/lockfile-lint-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"url": "https://github.com/lirantal/lockfile-lint.git"
},
"dependencies": {
"@yarnpkg/lockfile": "^1.1.0",
"@yarnpkg/parsers": "^3.0.0-rc.6",
"debug": "^4.1.1",
"object-hash": "^2.0.1"
},
Expand Down
59 changes: 50 additions & 9 deletions packages/lockfile-lint-api/src/ParseLockfile.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,68 @@
// @ts-check
/* eslint-disable security/detect-object-injection */
'use strict'

const fs = require('fs')
const path = require('path')
const yarnLockfileParser = require('@yarnpkg/lockfile')
const yarnParseSyml = require('@yarnpkg/parsers').parseSyml
const hash = require('object-hash')
const {ParsingError, ERROR_MESSAGES} = require('./common/ParsingError')
const {
NO_OPTIONS,
NO_LOCKFILE,
NO_PARSER_FOR_PATH,
NO_PARSER_FOR_TYPE,
READ_FAILED,
PARSE_NPMLOCKFILE_FAILED,
PARSE_YARNLOCKFILE_FAILED
} = ERROR_MESSAGES

/**
* Checks if a sample object is a valid dependency structure
* @return boolean
*/
function checkSampleContent (lockfile) {
const [sampleKey, sampleValue] = Object.entries(lockfile)[0]
return (
sampleKey.match(/.*@.*/) &&
(sampleValue &&
typeof sampleValue === 'object' &&
sampleValue.hasOwnProperty('version') &&
sampleValue.hasOwnProperty('resolved'))
)
}
/**
* @param {string|Buffer} lockfileBuffer - the lockfile contents
* @return {{ type: string, object: any }}
*/
function yarnParseAndVerify (lockfileBuffer) {
const lockfile = yarnParseSyml(lockfileBuffer.toString())
const hasSensibleContent =
lockfile && typeof lockfile === 'object' && checkSampleContent(lockfile)
if (!hasSensibleContent) {
throw Error('Lockfile does not seem to contain a valid dependency list')
}
return {type: 'success', object: lockfile}
}
class ParseLockfile {
/**
* constructor
* @param {string} options.lockfilePath - path to the lockfile
* @param {object} options
* @param {string} [options.lockfilePath] - path to the lockfile
* @param {string} [options.lockfileText] - utf-8 string content of the lockfile
* @param {string} options.lockfileType - the package manager type
* for lockfile
*/
constructor (options) {
if (!options || typeof options !== 'object') {
throw new ParsingError(NO_OPTIONS)
}
if (!options.lockfilePath && !options.lockfileText) {
throw new ParsingError(NO_LOCKFILE)
}

this.options = {}
this.options.lockfilePath = options.lockfilePath
this.options.lockfileText = options.lockfileText
this.options.lockfileType = options.lockfileType
}

Expand All @@ -54,11 +88,16 @@ class ParseLockfile {
}

let file
try {
// eslint-disable-next-line security/detect-non-literal-fs-filename
file = fs.readFileSync(this.options.lockfilePath, 'utf8')
} catch (error) {
throw new ParsingError(READ_FAILED, this.options.lockfilePath, error)
if (this.options.lockfileText) {
file = this.options.lockfileText
} else {
try {
const fs = require('fs')
// eslint-disable-next-line security/detect-non-literal-fs-filename
file = fs.readFileSync(this.options.lockfilePath, 'utf-8')
} catch (error) {
throw new ParsingError(READ_FAILED, this.options.lockfilePath, error)
}
}

return lockfileParser.call(this, file)
Expand Down Expand Up @@ -99,10 +138,12 @@ class ParseLockfile {
parseYarnLockfile (lockfileBuffer) {
let parsedFile
try {
parsedFile = yarnLockfileParser.parse(lockfileBuffer)
parsedFile = yarnParseAndVerify(lockfileBuffer)
} catch (error) {
console.log(error)
throw new ParsingError(PARSE_YARNLOCKFILE_FAILED, this.options.lockfilePath, error)
}
console.log(typeof parsedFile, parsedFile)
return parsedFile
}

Expand Down
3 changes: 2 additions & 1 deletion packages/lockfile-lint-api/src/common/ParsingError.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
const {LOCKFILE_TYPES} = require('./constants')

const ERROR_MESSAGES = {
NO_OPTIONS: () => 'Did not receive options for lockfile path or type',
NO_OPTIONS: () => 'Did not receive options for lockfile or type',
NO_LOCKFILE: () => 'Did not receive lockfile path or text',
NO_PARSER_FOR_TYPE: type =>
`Unable to find relevant lockfile parser for type "${type}", the currently available options are ${LOCKFILE_TYPES}.`,
NO_PARSER_FOR_PATH: path =>
Expand Down
21 changes: 21 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,14 @@
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==

"@yarnpkg/parsers@^3.0.0-rc.6":
version "3.0.0-rc.6"
resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-3.0.0-rc.6.tgz#3c93267fdae4470e4eaaf8c5f81d0d00ea177f76"
integrity sha512-YqtJ9VQqQixZsJJS4X83e6RMpgK1jmQJSIrCfd1wO3i/7vPk9QoLvvZS4bwZ2ha8QWqWlO/alAcXCGBezEI1Ig==
dependencies:
js-yaml "^3.10.0"
tslib "^1.13.0"

JSONStream@^1.0.4, JSONStream@^1.3.4, JSONStream@^1.3.5:
version "1.3.5"
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
Expand Down Expand Up @@ -5732,6 +5740,14 @@ js-tokens@^3.0.2:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=

js-yaml@^3.10.0:
version "3.14.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"

js-yaml@^3.13.0, js-yaml@^3.13.1:
version "3.13.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
Expand Down Expand Up @@ -9404,6 +9420,11 @@ trim-right@^1.0.1:
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=

tslib@^1.13.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==

tslib@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
Expand Down

0 comments on commit 18c6ae0

Please sign in to comment.