本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
1、源码
'use strict'
//?: 匹配 pattern 但不获取匹配结果
var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')
var builtins = require('builtins')
var blacklist = [ 'node_modules', 'favicon.ico',]
function validate (name) {
var warnings = []
var errors = []
//是否为空
if (name === null) {
errors.push('name cannot be null')
return done(warnings, errors)
}
//是否未定义
if (name === undefined) {
errors.push('name cannot be undefined')
return done(warnings, errors)
}
//是否非字符串
if (typeof name !== 'string') {
errors.push('name must be a string')
return done(warnings, errors)
}
//是否长度为0
if (!name.length) {
errors.push('name length must be greater than zero')
}
//是否以.开头
if (name.match(/^./)) {
errors.push('name cannot start with a period')
}
//是否以_开头
if (name.match(/^_/)) {
errors.push('name cannot start with an underscore')
}
//是否有空格
if (name.trim() !== name) {
errors.push('name cannot contain leading or trailing spaces')
}
// 是否与保留/列入黑名单的名称相同
blacklist.forEach(function (blacklistedName) {
if (name.toLowerCase() === blacklistedName) {
errors.push(blacklistedName + ' is a blacklisted name')
}
})
//是否与节点.js/io.js核心模块相同
builtins({ version: '*' }).forEach(function (builtin) {
if (name.toLowerCase() === builtin) {
warnings.push(builtin + ' is a core module name')
}
})
//是否长度大于214
if (name.length > 214) {
warnings.push('name can no longer contain more than 214 characters')
}
// 是否小写
if (name.toLowerCase() !== name) {
warnings.push('name can no longer contain capital letters')
}
//是否包含~'!()*
if (/[~'!()*]/.test(name.split('/').slice(-1)[0])) {
warnings.push('name can no longer contain special characters ("~'!()*")')
}
if (encodeURIComponent(name) !== name) {
// Maybe it's a scoped package name, like @user/package
var nameMatch = name.match(scopedPackagePattern)
if (nameMatch) {
var user = nameMatch[1]
var pkg = nameMatch[2]
if (encodeURIComponent(user) === user && encodeURIComponent(pkg) === pkg) {
return done(warnings, errors)
}
}
errors.push('name can only contain URL-friendly characters')
}
return done(warnings, errors)
}
var done = function (warnings, errors) {
var result = {
validForNewPackages: errors.length === 0 && warnings.length === 0,
validForOldPackages: errors.length === 0,
warnings: warnings,
errors: errors,
}
if (!result.warnings.length) {
delete result.warnings
}
if (!result.errors.length) {
delete result.errors
}
return result
}
module.exports = validate
builtin核心模块:
'assert','buffer','child_process','cluster','console','constants','crypto','dgram','dns','domain','events','fs','http','https','module','net','os','path','punycode','querystring','readline','repl','stream','string_decoder','sys','timers','tls','tty','url','util','vm','zlib'
2、注意
encodeURIComponent() 函数通过将一个,两个,三个或四个表示字符的 UTF-8 编码的转义序列替换某些字符的每个实例来编码 URI
var set1 = ";,/?:@&=+$"; // 保留字符
var set2 = "-_.!~*'()"; // 不转义字符
var set3 = "#"; // 数字标志
var set4 = "ABC abc 123"; // 字母数字字符和空格
console.log(encodeURI(set1)); // ;,/?:@&=+$
console.log(encodeURI(set2)); // -_.!~*'()
console.log(encodeURI(set3)); // #
console.log(encodeURI(set4)); // ABC%20abc%20123 (空格被编码为 %20)
console.log(encodeURIComponent(set1)); // %3B%2C%2F%3F%3A%40%26%3D%2B%24
console.log(encodeURIComponent(set2)); // -_.!~*'()
console.log(encodeURIComponent(set3)); // %23
console.log(encodeURIComponent(set4)); // ABC%20abc%20123 (the space gets encoded as %20)
3、测试
test('validate-npm-package-name', function (t) {
// Traditional
t.same(validate('some-package'), { validForNewPackages: true, validForOldPackages: true })
t.same(validate('example.com'), { validForNewPackages: true, validForOldPackages: true })
t.same(validate('under_score'), { validForNewPackages: true, validForOldPackages: true })
t.same(validate('period.js'), { validForNewPackages: true, validForOldPackages: true })
t.same(validate('123numeric'), { validForNewPackages: true, validForOldPackages: true })
t.same(validate('crazy!'), {
validForNewPackages: false,
validForOldPackages: true,
warnings: ['name can no longer contain special characters ("~'!()*")'],
})
// Scoped (npm 2+)
t.same(validate('@npm/thingy'), { validForNewPackages: true, validForOldPackages: true })
t.same(validate('@npm-zors/money!time.js'), {
validForNewPackages: false,
validForOldPackages: true,
warnings: ['name can no longer contain special characters ("~'!()*")'],
})
// Invalid
t.same(validate(''), {
validForNewPackages: false,
validForOldPackages: false,
errors: ['name length must be greater than zero'] })
t.same(validate(''), {
validForNewPackages: false,
validForOldPackages: false,
errors: ['name length must be greater than zero'] })
t.same(validate('.start-with-period'), {
validForNewPackages: false,
validForOldPackages: false,
errors: ['name cannot start with a period'] })
t.same(validate('_start-with-underscore'), {
validForNewPackages: false,
validForOldPackages: false,
errors: ['name cannot start with an underscore'] })
t.same(validate('contain:colons'), {
validForNewPackages: false,
validForOldPackages: false,
errors: ['name can only contain URL-friendly characters'] })
t.same(validate(' leading-space'), {
validForNewPackages: false,
validForOldPackages: false,
/* eslint-disable-next-line max-len */
errors: ['name cannot contain leading or trailing spaces', 'name can only contain URL-friendly characters'] })
t.same(validate('trailing-space '), {
validForNewPackages: false,
validForOldPackages: false,
/* eslint-disable-next-line max-len */
errors: ['name cannot contain leading or trailing spaces', 'name can only contain URL-friendly characters'] })
t.same(validate('s/l/a/s/h/e/s'), {
validForNewPackages: false,
validForOldPackages: false,
errors: ['name can only contain URL-friendly characters'] })
t.same(validate('node_modules'), {
validForNewPackages: false,
validForOldPackages: false,
errors: ['node_modules is a blacklisted name'] })
t.same(validate('favicon.ico'), {
validForNewPackages: false,
validForOldPackages: false,
errors: ['favicon.ico is a blacklisted name'] })
// Node/IO Core
t.same(validate('http'), {
validForNewPackages: false,
validForOldPackages: true,
warnings: ['http is a core module name'] })
t.deepEqual(validate('process'), {
validForNewPackages: false,
validForOldPackages: true,
warnings: ['process is a core module name'] })
// Long Package Names
/* eslint-disable-next-line max-len */
t.same(validate('ifyouwanttogetthesumoftwonumberswherethosetwonumbersarechosenbyfindingthelargestoftwooutofthreenumbersandsquaringthemwhichismultiplyingthembyitselfthenyoushouldinputthreenumbersintothisfunctionanditwilldothatforyou-'), {
validForNewPackages: false,
validForOldPackages: true,
warnings: ['name can no longer contain more than 214 characters'],
})
/* eslint-disable-next-line max-len */
t.same(validate('ifyouwanttogetthesumoftwonumberswherethosetwonumbersarechosenbyfindingthelargestoftwooutofthreenumbersandsquaringthemwhichismultiplyingthembyitselfthenyoushouldinputthreenumbersintothisfunctionanditwilldothatforyou'), {
validForNewPackages: true,
validForOldPackages: true,
})
// Legacy Mixed-Case
t.same(validate('CAPITAL-LETTERS'), {
validForNewPackages: false,
validForOldPackages: true,
warnings: ['name can no longer contain capital letters'] })
t.end()
})