【源码学习】探索validate-npm-package-name 检测 npm 包是否符合标准之旅【5】

简介

本文档记录了我参加 validate-npm-package-name 检测 npm 包是否符合标准共读的过程中的学习和思考。

参与目的:

  • 探索源码,理解工作原理
  • 学习和成长
  • 提升技术水平

作为一名前端开发者,参与这次源码共读活动。这是一个很好的机会,与志同道合的开发者一起深入探索源码、理解工作原理,并在过程中学习和成长。

本文参与学习的目标:

源码分析

validate-npm-package-name是一个用于验证npm包名称是否符合规范的npm包。 它的源代码主要包含以下几个部分:

  1. 包名称验证逻辑:这部分代码主要负责解析npm包名称,并检查其是否符合规范。它使用了正则表达式来匹配包名称的格式,例如以字母或数字开头,后面可以包含字母、数字和下划线,但不能以点号开头或结尾等。
  2. 错误处理逻辑:当验证失败时,validate-npm-package-name会抛出异常或返回错误信息。这部分代码包括异常处理、错误信息输出和日志记录等功能。
  3. 依赖库:validate-npm-package-name使用了多个第三方库,如lodashaxios等,用于辅助实现其功能。这些库的作用包括字符串处理、网络请求等。

源码准备

Bash 复制代码
git clone https://github.com/npm/validate-npm-package-name

有效名称

JavaScript 复制代码
var validate = require("validate-npm-package-name")

validate("some-package")
validate("example.com")
validate("under_score")
validate("123numeric")
validate("@npm/thingy")
validate("@jane/foo.js")

上述所有名称都有效,因此您将取回此对象:

JavaScript 复制代码
{
  validForNewPackages: true,
  validForOldPackages: true
}

无效名称

JavaScript 复制代码
validate("excited!")
validate(" leading-space:and:weirdchars")

这从来都不是一个有效的软件包名称,所以你会得到这个:

JavaScript 复制代码
{
  validForNewPackages: false,
  validForOldPackages: false,
  errors: [
    'name cannot contain leading or trailing spaces',
    'name can only contain URL-friendly characters'
  ]
}

源码详情

代码结构

Javascript 复制代码
'use strict'
const { builtinModules: builtins } = require('module')

// 正则表达式
var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')

// 黑名单数组
var blacklist = [
  'node_modules',
  'favicon.ico',
]

// 用于验证输入的名称是否符合特定的规则
function validate (name) {...}

// 对传入的警告和错误进行检查,并返回一个包含一些属性的对象
var done = function (warnings, errors) {...}

// 导出
module.exports = validate

正则表达式

javascript 复制代码
var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')

这个正则表达式的模式是:

  • ^:表示匹配字符串的开始。
  • (?:@([^/]+?)[/])?:这部分用于匹配命名空间。@ 是一个特殊的字符,通常用于标识包的名称中的命名空间。括号中的 ([^/]+?) 匹配任意数量的非斜杠字符,尽可能少的匹配。这部分还可以捕获命名空间部分,如果包名后面没有斜杠,这部分会匹配到包名的结束。
  • ?:表示前面的部分是可选的。也就是说,包名可以没有命名空间,直接是另一个部分(这部分被 ([^/]+?) 捕获)。
  • ([^/]+?):这部分用于匹配包名的主要部分,尽可能少的匹配。
  • $:表示匹配字符串的结束。

所以,这个正则表达式可以匹配像 @John/Awesome 这样的包名,其中 John 是命名空间,Awesome 是包名的主要部分。如果包名后面没有斜杠,那么命名空间部分会匹配到 John/ 结束。 然后,var blacklist = ['node_modules', 'favicon.ico'] 这行代码创建了一个数组,其中包含两个字符串值:'node_modules' 和 'favicon.ico'。这个数组可能用于过滤不应该被正则表达式匹配到的包名

总的来说,这段代码可能是在处理JavaScript项目中的包管理问题,例如解析npm包名称或处理scoped包。

validate 函数

javascript 复制代码
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)
  }

  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')
  }

  // No funny business
  blacklist.forEach(function (blacklistedName) {
    if (name.toLowerCase() === blacklistedName) {
      errors.push(blacklistedName + ' is a blacklisted name')
    }
  })

  // Generate warnings for stuff that used to be allowed

  // core module names like http, events, util, etc
  if (builtins.includes(name.toLowerCase())) {
    warnings.push(name + ' is a core module name')
  }

  if (name.length > 214) {
    warnings.push('name can no longer contain more than 214 characters')
  }

  // mIxeD CaSe nAMEs
  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)
}

用于验证输入的 name 参数是否满足一系列特定的条件,条件如下:

  1. name 不能为 nullundefined
  2. name 必须是一个字符串。
  3. name 的长度必须大于零。
  4. name 不能以句点 (.)、下划线 (_) 或其他特定字符开始。
  5. name 不能有首尾空格。
  6. name 不能包含前后的斜杠 (/)。
  7. name 不能是禁止的名称(由一个黑名单列表定义)。
  8. 旧有的标准模块名可能仍然是合法的,但可能会收到警告。
  9. 如果 name 的长度超过 214 个字符,将收到警告。
  10. 如果 name 中的大写字母或特殊字符(如 ~'!()*)存在,将收到警告。
  11. 如果 name 不符合 URL 友好的字符规则(即编码后的 name 与原始的 name 不匹配),则可能是一个特定位符号或者类似 "@user/package" 这样的 scoped package 名称,这也会导致警告。

done 函数

它接受两个参数 warningserrors,并返回一个对象。这个对象包含了一些关于输入参数的验证结果,以及一些警告和错误信息。

Javascript 复制代码
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
}
  • 首先,它检查 warningserrors 数组的长度。如果它们都为空(即长度为 0),那么函数会返回一个对象,该对象表示所有输入都是有效的。
  • 如果 warnings 数组中有元素,那么这些元素会被存储在 result.warnings 中。
  • 如果 errors 数组中有元素,那么这些元素会被存储在 result.errors 中。
  • 如果 result.warningsresult.errors 的长度为 0,那么它们将被从 result 对象中删除。这是为了减少不必要的存储空间,防止出现大量的警告和错误信息而影响性能。
  • 最后,这个函数会返回包含所有有效性的验证结果和警告和错误信息的对象。

总结

通过阅读这段代码,我学习到了 JavaScript 中函数的基本结构和逻辑,同时也了解了如何在 JavaScript 中处理输入参数的验证。

总的来说,这段代码相对来说不难

相关推荐
拉不动的猪8 分钟前
刷刷题28(http)
前端·javascript·面试
IT、木易1 小时前
大白话 CSS 中transform属性的常见变换类型(平移、旋转、缩放等)及使用场景
前端·css·面试
1024小神1 小时前
更改github action工作流的权限
前端·javascript
Epicurus1 小时前
JavaScript无阻塞加载的方式
前端·javascript
1024小神1 小时前
tauri程序使用github action发布linux中arm架构
前端·javascript
ahhdfjfdf1 小时前
最全的`Map` 和 `WeakMap`的区别
前端
JYeontu1 小时前
实现一个带@功能的输入框组件
前端·javascript·vue.js
一颗奇趣蛋2 小时前
vue-router的query和params的区别(附实际用法)
前端·vue.js
孤城2862 小时前
MAC电脑常用操作
前端·macos·快捷键·新手·电脑使用
木亦Sam2 小时前
Vue DevTools逆向工程:自己实现一个组件热更新调试器
前端