1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package cache
6
7import (
8	"context"
9	"fmt"
10	"go/ast"
11	"go/token"
12	"os"
13	"path/filepath"
14	"sync"
15
16	"golang.org/x/tools/go/analysis"
17	"golang.org/x/tools/go/packages"
18	"golang.org/x/tools/internal/lsp/source"
19	"golang.org/x/tools/internal/lsp/telemetry"
20	"golang.org/x/tools/internal/span"
21	"golang.org/x/tools/internal/telemetry/log"
22	errors "golang.org/x/xerrors"
23)
24
25type snapshot struct {
26	id   uint64
27	view *view
28
29	// mu guards all of the maps in the snapshot.
30	mu sync.Mutex
31
32	// ids maps file URIs to package IDs.
33	// It may be invalidated on calls to go/packages.
34	ids map[span.URI][]packageID
35
36	// metadata maps file IDs to their associated metadata.
37	// It may invalidated on calls to go/packages.
38	metadata map[packageID]*metadata
39
40	// importedBy maps package IDs to the list of packages that import them.
41	importedBy map[packageID][]packageID
42
43	// files maps file URIs to their corresponding FileHandles.
44	// It may invalidated when a file's content changes.
45	files map[span.URI]source.FileHandle
46
47	// packages maps a packageKey to a set of packageHandles to which that file belongs.
48	// It may be invalidated when a file's content changes.
49	packages map[packageKey]*packageHandle
50
51	// actions maps an actionkey to its actionHandle.
52	actions map[actionKey]*actionHandle
53
54	// workspacePackages contains the workspace's packages, which are loaded
55	// when the view is created.
56	workspacePackages map[packageID]packagePath
57
58	// unloadableFiles keeps track of files that we've failed to load.
59	unloadableFiles map[span.URI]struct{}
60}
61
62type packageKey struct {
63	mode source.ParseMode
64	id   packageID
65}
66
67type actionKey struct {
68	pkg      packageKey
69	analyzer *analysis.Analyzer
70}
71
72func (s *snapshot) ID() uint64 {
73	return s.id
74}
75
76func (s *snapshot) View() source.View {
77	return s.view
78}
79
80// Config returns the configuration used for the snapshot's interaction with the
81// go/packages API.
82func (s *snapshot) Config(ctx context.Context) *packages.Config {
83	env, buildFlags := s.view.env()
84	cfg := &packages.Config{
85		Env:        env,
86		Dir:        s.view.folder.Filename(),
87		Context:    ctx,
88		BuildFlags: buildFlags,
89		Mode: packages.NeedName |
90			packages.NeedFiles |
91			packages.NeedCompiledGoFiles |
92			packages.NeedImports |
93			packages.NeedDeps |
94			packages.NeedTypesSizes,
95		Fset:    s.view.session.cache.fset,
96		Overlay: s.buildOverlay(),
97		ParseFile: func(*token.FileSet, string, []byte) (*ast.File, error) {
98			panic("go/packages must not be used to parse files")
99		},
100		Logf: func(format string, args ...interface{}) {
101			if s.view.options.VerboseOutput {
102				log.Print(ctx, fmt.Sprintf(format, args...))
103			}
104		},
105		Tests: true,
106	}
107	return cfg
108}
109
110func (s *snapshot) buildOverlay() map[string][]byte {
111	s.mu.Lock()
112	defer s.mu.Unlock()
113
114	overlays := make(map[string][]byte)
115	for uri, fh := range s.files {
116		overlay, ok := fh.(*overlay)
117		if !ok {
118			continue
119		}
120		if overlay.saved {
121			continue
122		}
123		// TODO(rstambler): Make sure not to send overlays outside of the current view.
124		overlays[uri.Filename()] = overlay.text
125	}
126	return overlays
127}
128
129func (s *snapshot) PackageHandles(ctx context.Context, fh source.FileHandle) ([]source.PackageHandle, error) {
130	if fh.Identity().Kind != source.Go {
131		panic(fmt.Sprintf("called PackageHandles on a non-Go FileHandle"))
132	}
133
134	ctx = telemetry.File.With(ctx, fh.Identity().URI)
135
136	// Check if we should reload metadata for the file. We don't invalidate IDs
137	// (though we should), so the IDs will be a better source of truth than the
138	// metadata. If there are no IDs for the file, then we should also reload.
139	ids := s.getIDsForURI(fh.Identity().URI)
140	reload := len(ids) == 0
141	for _, id := range ids {
142		// Reload package metadata if any of the metadata has missing
143		// dependencies, in case something has changed since the last time we
144		// reloaded it.
145		if m := s.getMetadata(id); m == nil || len(m.missingDeps) > 0 {
146			reload = true
147			break
148		}
149	}
150	if reload {
151		if err := s.load(ctx, fileURI(fh.Identity().URI)); err != nil {
152			return nil, err
153		}
154	}
155	// Get the list of IDs from the snapshot again, in case it has changed.
156	var phs []source.PackageHandle
157	for _, id := range s.getIDsForURI(fh.Identity().URI) {
158		ph, err := s.packageHandle(ctx, id, source.ParseFull)
159		if err != nil {
160			return nil, err
161		}
162		phs = append(phs, ph)
163	}
164	return phs, nil
165}
166
167// packageHandle returns a PackageHandle for the given ID. It assumes that
168// the metadata for the given ID has already been loaded, but if the
169// PackageHandle has not been constructed, it will rebuild it.
170func (s *snapshot) packageHandle(ctx context.Context, id packageID, mode source.ParseMode) (*packageHandle, error) {
171	ph := s.getPackage(id, mode)
172	if ph != nil {
173		return ph, nil
174	}
175	// Don't reload metadata in this function.
176	// Callers of this function must reload metadata themselves.
177	m := s.getMetadata(id)
178	if m == nil {
179		return nil, errors.Errorf("%s has no metadata", id)
180	}
181	return s.buildPackageHandle(ctx, m.id, mode)
182}
183
184func (s *snapshot) GetReverseDependencies(ctx context.Context, id string) ([]source.PackageHandle, error) {
185	if err := s.awaitLoaded(ctx); err != nil {
186		return nil, err
187	}
188	ids := make(map[packageID]struct{})
189	s.transitiveReverseDependencies(packageID(id), ids)
190
191	// Make sure to delete the original package ID from the map.
192	delete(ids, packageID(id))
193
194	var results []source.PackageHandle
195	for id := range ids {
196		ph, err := s.packageHandle(ctx, id, source.ParseFull)
197		if err != nil {
198			return nil, err
199		}
200		results = append(results, ph)
201	}
202	return results, nil
203}
204
205// transitiveReverseDependencies populates the uris map with file URIs
206// belonging to the provided package and its transitive reverse dependencies.
207func (s *snapshot) transitiveReverseDependencies(id packageID, ids map[packageID]struct{}) {
208	if _, ok := ids[id]; ok {
209		return
210	}
211	if s.getMetadata(id) == nil {
212		return
213	}
214	ids[id] = struct{}{}
215	importedBy := s.getImportedBy(id)
216	for _, parentID := range importedBy {
217		s.transitiveReverseDependencies(parentID, ids)
218	}
219}
220
221func (s *snapshot) getImportedBy(id packageID) []packageID {
222	s.mu.Lock()
223	defer s.mu.Unlock()
224	return s.getImportedByLocked(id)
225}
226
227func (s *snapshot) getImportedByLocked(id packageID) []packageID {
228	// If we haven't rebuilt the import graph since creating the snapshot.
229	if len(s.importedBy) == 0 {
230		s.rebuildImportGraph()
231	}
232	return s.importedBy[id]
233}
234
235func (s *snapshot) clearAndRebuildImportGraph() {
236	s.mu.Lock()
237	defer s.mu.Unlock()
238
239	// Completely invalidate the original map.
240	s.importedBy = make(map[packageID][]packageID)
241	s.rebuildImportGraph()
242}
243
244func (s *snapshot) rebuildImportGraph() {
245	for id, m := range s.metadata {
246		for _, importID := range m.deps {
247			s.importedBy[importID] = append(s.importedBy[importID], id)
248		}
249	}
250}
251
252func (s *snapshot) addPackage(ph *packageHandle) {
253	s.mu.Lock()
254	defer s.mu.Unlock()
255
256	// TODO: We should make sure not to compute duplicate packageHandles,
257	// and instead panic here. This will be hard to do because we may encounter
258	// the same package multiple times in the dependency tree.
259	if _, ok := s.packages[ph.packageKey()]; ok {
260		return
261	}
262	s.packages[ph.packageKey()] = ph
263}
264
265func (s *snapshot) workspacePackageIDs() (ids []packageID) {
266	s.mu.Lock()
267	defer s.mu.Unlock()
268
269	for id := range s.workspacePackages {
270		ids = append(ids, id)
271	}
272	return ids
273}
274
275func (s *snapshot) WorkspacePackages(ctx context.Context) ([]source.PackageHandle, error) {
276	if err := s.awaitLoaded(ctx); err != nil {
277		return nil, err
278	}
279	var results []source.PackageHandle
280	for _, pkgID := range s.workspacePackageIDs() {
281		ph, err := s.packageHandle(ctx, pkgID, source.ParseFull)
282		if err != nil {
283			return nil, err
284		}
285		results = append(results, ph)
286	}
287	return results, nil
288}
289
290func (s *snapshot) KnownPackages(ctx context.Context) ([]source.PackageHandle, error) {
291	if err := s.awaitLoaded(ctx); err != nil {
292		return nil, err
293	}
294	// Collect PackageHandles for all of the workspace packages first.
295	// They may need to be reloaded if their metadata has been invalidated.
296	wsPackages := make(map[packageID]bool)
297	s.mu.Lock()
298	for id := range s.workspacePackages {
299		wsPackages[id] = true
300	}
301	s.mu.Unlock()
302
303	var results []source.PackageHandle
304	for pkgID := range wsPackages {
305		ph, err := s.packageHandle(ctx, pkgID, source.ParseFull)
306		if err != nil {
307			return nil, err
308		}
309		results = append(results, ph)
310	}
311
312	// Once all workspace packages have been checked, the metadata will be up-to-date.
313	// Add all packages known in the workspace (that haven't already been added).
314	pkgIDs := make(map[packageID]bool)
315	s.mu.Lock()
316	for id := range s.metadata {
317		if !wsPackages[id] {
318			pkgIDs[id] = true
319		}
320	}
321	s.mu.Unlock()
322
323	for pkgID := range pkgIDs {
324		// Metadata for these packages should already be up-to-date,
325		// so just build the package handle directly (without a reload).
326		ph, err := s.buildPackageHandle(ctx, pkgID, source.ParseExported)
327		if err != nil {
328			return nil, err
329		}
330		results = append(results, ph)
331	}
332	return results, nil
333}
334
335func (s *snapshot) CachedImportPaths(ctx context.Context) (map[string]source.Package, error) {
336	// Don't reload workspace package metadata.
337	// This function is meant to only return currently cached information.
338	s.view.awaitInitialized(ctx)
339
340	s.mu.Lock()
341	defer s.mu.Unlock()
342
343	results := map[string]source.Package{}
344	for _, ph := range s.packages {
345		cachedPkg, err := ph.cached()
346		if err != nil {
347			continue
348		}
349		for importPath, newPkg := range cachedPkg.imports {
350			if oldPkg, ok := results[string(importPath)]; ok {
351				// Using the same trick as NarrowestPackageHandle, prefer non-variants.
352				if len(newPkg.compiledGoFiles) < len(oldPkg.(*pkg).compiledGoFiles) {
353					results[string(importPath)] = newPkg
354				}
355			} else {
356				results[string(importPath)] = newPkg
357			}
358		}
359	}
360	return results, nil
361}
362
363func (s *snapshot) getPackage(id packageID, mode source.ParseMode) *packageHandle {
364	s.mu.Lock()
365	defer s.mu.Unlock()
366
367	key := packageKey{
368		id:   id,
369		mode: mode,
370	}
371	return s.packages[key]
372}
373
374func (s *snapshot) getActionHandle(id packageID, m source.ParseMode, a *analysis.Analyzer) *actionHandle {
375	s.mu.Lock()
376	defer s.mu.Unlock()
377
378	key := actionKey{
379		pkg: packageKey{
380			id:   id,
381			mode: m,
382		},
383		analyzer: a,
384	}
385	return s.actions[key]
386}
387
388func (s *snapshot) addActionHandle(ah *actionHandle) {
389	s.mu.Lock()
390	defer s.mu.Unlock()
391
392	key := actionKey{
393		analyzer: ah.analyzer,
394		pkg: packageKey{
395			id:   ah.pkg.id,
396			mode: ah.pkg.mode,
397		},
398	}
399	if _, ok := s.actions[key]; ok {
400		return
401	}
402	s.actions[key] = ah
403}
404
405func (s *snapshot) getIDsForURI(uri span.URI) []packageID {
406	s.mu.Lock()
407	defer s.mu.Unlock()
408
409	return s.ids[uri]
410}
411
412func (s *snapshot) getMetadataForURILocked(uri span.URI) (metadata []*metadata) {
413	// TODO(matloob): uri can be a file or directory. Should we update the mappings
414	// to map directories to their contained packages?
415
416	for _, id := range s.ids[uri] {
417		if m, ok := s.metadata[id]; ok {
418			metadata = append(metadata, m)
419		}
420	}
421	return metadata
422}
423
424func (s *snapshot) getMetadata(id packageID) *metadata {
425	s.mu.Lock()
426	defer s.mu.Unlock()
427
428	return s.metadata[id]
429}
430
431func (s *snapshot) addID(uri span.URI, id packageID) {
432	s.mu.Lock()
433	defer s.mu.Unlock()
434
435	for _, existingID := range s.ids[uri] {
436		if existingID == id {
437			// TODO: We should make sure not to set duplicate IDs,
438			// and instead panic here. This can be done by making sure not to
439			// reset metadata information for packages we've already seen.
440			return
441		}
442	}
443	s.ids[uri] = append(s.ids[uri], id)
444}
445
446func (s *snapshot) isWorkspacePackage(id packageID) (packagePath, bool) {
447	s.mu.Lock()
448	defer s.mu.Unlock()
449
450	scope, ok := s.workspacePackages[id]
451	return scope, ok
452}
453
454// GetFile returns a File for the given URI. It will always succeed because it
455// adds the file to the managed set if needed.
456func (s *snapshot) GetFile(uri span.URI) (source.FileHandle, error) {
457	f, err := s.view.getFile(uri)
458	if err != nil {
459		return nil, err
460	}
461
462	s.mu.Lock()
463	defer s.mu.Unlock()
464
465	if _, ok := s.files[f.URI()]; !ok {
466		s.files[f.URI()] = s.view.session.cache.GetFile(uri)
467	}
468	return s.files[f.URI()], nil
469}
470
471func (s *snapshot) IsOpen(uri span.URI) bool {
472	s.mu.Lock()
473	defer s.mu.Unlock()
474
475	_, open := s.files[uri].(*overlay)
476	return open
477}
478
479func (s *snapshot) awaitLoaded(ctx context.Context) error {
480	// Do not return results until the snapshot's view has been initialized.
481	s.view.awaitInitialized(ctx)
482
483	if err := s.reloadWorkspace(ctx); err != nil {
484		return err
485	}
486	return s.reloadOrphanedFiles(ctx)
487}
488
489// reloadWorkspace reloads the metadata for all invalidated workspace packages.
490func (s *snapshot) reloadWorkspace(ctx context.Context) error {
491	// If the view's build configuration is invalid, we cannot reload by package path.
492	// Just reload the directory instead.
493	if !s.view.hasValidBuildConfiguration {
494		return s.load(ctx, viewLoadScope("LOAD_INVALID_VIEW"))
495	}
496
497	// See which of the workspace packages are missing metadata.
498	s.mu.Lock()
499	var pkgPaths []interface{}
500	for id, pkgPath := range s.workspacePackages {
501		if s.metadata[id] == nil {
502			pkgPaths = append(pkgPaths, pkgPath)
503		}
504	}
505	s.mu.Unlock()
506
507	if len(pkgPaths) == 0 {
508		return nil
509	}
510	return s.load(ctx, pkgPaths...)
511}
512
513func (s *snapshot) reloadOrphanedFiles(ctx context.Context) error {
514	// When we load ./... or a package path directly, we may not get packages
515	// that exist only in overlays. As a workaround, we search all of the files
516	// available in the snapshot and reload their metadata individually using a
517	// file= query if the metadata is unavailable.
518	scopes := s.orphanedFileScopes()
519	if len(scopes) == 0 {
520		return nil
521	}
522
523	err := s.load(ctx, scopes...)
524
525	// If we failed to load some files, i.e. they have no metadata,
526	// mark the failures so we don't bother retrying until the file's
527	// content changes.
528	//
529	// TODO(rstambler): This may be an overestimate if the load stopped
530	// early for an unrelated errors. Add a fallback?
531	//
532	// Check for context cancellation so that we don't incorrectly mark files
533	// as unloadable, but don't return before setting all workspace packages.
534	if ctx.Err() == nil && err != nil {
535		s.mu.Lock()
536		for _, scope := range scopes {
537			uri := span.URI(scope.(fileURI))
538			if s.getMetadataForURILocked(uri) == nil {
539				s.unloadableFiles[uri] = struct{}{}
540			}
541		}
542		s.mu.Unlock()
543	}
544	return nil
545}
546
547func (s *snapshot) orphanedFileScopes() []interface{} {
548	s.mu.Lock()
549	defer s.mu.Unlock()
550
551	scopeSet := make(map[span.URI]struct{})
552	for uri, fh := range s.files {
553		// Don't try to reload metadata for go.mod files.
554		if fh.Identity().Kind != source.Go {
555			continue
556		}
557		// If the URI doesn't belong to this view, then it's not in a workspace
558		// package and should not be reloaded directly.
559		if !contains(s.view.session.viewsOf(uri), s.view) {
560			continue
561		}
562		// Don't reload metadata for files we've already deemed unloadable.
563		if _, ok := s.unloadableFiles[uri]; ok {
564			continue
565		}
566		if s.getMetadataForURILocked(uri) == nil {
567			scopeSet[uri] = struct{}{}
568		}
569	}
570	var scopes []interface{}
571	for uri := range scopeSet {
572		scopes = append(scopes, fileURI(uri))
573	}
574	return scopes
575}
576
577func contains(views []*view, view *view) bool {
578	for _, v := range views {
579		if v == view {
580			return true
581		}
582	}
583	return false
584}
585
586func (s *snapshot) clone(ctx context.Context, withoutURIs map[span.URI]source.FileHandle) *snapshot {
587	s.mu.Lock()
588	defer s.mu.Unlock()
589
590	result := &snapshot{
591		id:                s.id + 1,
592		view:              s.view,
593		ids:               make(map[span.URI][]packageID),
594		importedBy:        make(map[packageID][]packageID),
595		metadata:          make(map[packageID]*metadata),
596		packages:          make(map[packageKey]*packageHandle),
597		actions:           make(map[actionKey]*actionHandle),
598		files:             make(map[span.URI]source.FileHandle),
599		workspacePackages: make(map[packageID]packagePath),
600		unloadableFiles:   make(map[span.URI]struct{}),
601	}
602
603	// Copy all of the FileHandles.
604	for k, v := range s.files {
605		result.files[k] = v
606	}
607	// Copy the set of unloadable files.
608	for k, v := range s.unloadableFiles {
609		result.unloadableFiles[k] = v
610	}
611
612	// transitiveIDs keeps track of transitive reverse dependencies.
613	// If an ID is present in the map, invalidate its types.
614	// If an ID's value is true, invalidate its metadata too.
615	transitiveIDs := make(map[packageID]bool)
616
617	for withoutURI, currentFH := range withoutURIs {
618		directIDs := map[packageID]struct{}{}
619
620		// Collect all of the package IDs that correspond to the given file.
621		// TODO: if the file has moved into a new package, we should invalidate that too.
622		for _, id := range s.ids[withoutURI] {
623			directIDs[id] = struct{}{}
624		}
625		// The original FileHandle for this URI is cached on the snapshot.
626		originalFH := s.files[withoutURI]
627
628		// Check if the file's package name or imports have changed,
629		// and if so, invalidate this file's packages' metadata.
630		invalidateMetadata := s.shouldInvalidateMetadata(ctx, originalFH, currentFH)
631
632		// If a go.mod file's contents have changed, invalidate the metadata
633		// for all of the packages in the workspace.
634		if invalidateMetadata && currentFH.Identity().Kind == source.Mod {
635			for id := range s.workspacePackages {
636				directIDs[id] = struct{}{}
637			}
638		}
639
640		// If this is a file we don't yet know about,
641		// then we do not yet know what packages it should belong to.
642		// Make a rough estimate of what metadata to invalidate by finding the package IDs
643		// of all of the files in the same directory as this one.
644		// TODO(rstambler): Speed this up by mapping directories to filenames.
645		if len(directIDs) == 0 {
646			if dirStat, err := os.Stat(filepath.Dir(withoutURI.Filename())); err == nil {
647				for uri := range s.files {
648					if fdirStat, err := os.Stat(filepath.Dir(uri.Filename())); err == nil {
649						if os.SameFile(dirStat, fdirStat) {
650							for _, id := range s.ids[uri] {
651								directIDs[id] = struct{}{}
652							}
653						}
654					}
655				}
656			}
657		}
658
659		// Invalidate reverse dependencies too.
660		// TODO(heschi): figure out the locking model and use transitiveReverseDeps?
661		var addRevDeps func(packageID)
662		addRevDeps = func(id packageID) {
663			if _, seen := transitiveIDs[id]; seen {
664				return
665			}
666			transitiveIDs[id] = invalidateMetadata
667			for _, rid := range s.getImportedByLocked(id) {
668				addRevDeps(rid)
669			}
670		}
671		for id := range directIDs {
672			addRevDeps(id)
673		}
674
675		// Handle the invalidated file; it may have new contents or not exist.
676		if _, _, err := currentFH.Read(ctx); os.IsNotExist(err) {
677			delete(result.files, withoutURI)
678		} else {
679			result.files[withoutURI] = currentFH
680		}
681		// Make sure to remove the changed file from the unloadable set.
682		delete(result.unloadableFiles, withoutURI)
683	}
684
685	// Collect the IDs for the packages associated with the excluded URIs.
686	for k, ids := range s.ids {
687		result.ids[k] = ids
688	}
689	// Copy the set of initally loaded packages.
690	for k, v := range s.workspacePackages {
691		result.workspacePackages[k] = v
692	}
693	// Copy the package type information.
694	for k, v := range s.packages {
695		if _, ok := transitiveIDs[k.id]; ok {
696			continue
697		}
698		result.packages[k] = v
699	}
700	// Copy the package analysis information.
701	for k, v := range s.actions {
702		if _, ok := transitiveIDs[k.pkg.id]; ok {
703			continue
704		}
705		result.actions[k] = v
706	}
707	// Copy the package metadata. We only need to invalidate packages directly
708	// containing the affected file, and only if it changed in a relevant way.
709	for k, v := range s.metadata {
710		if invalidateMetadata, ok := transitiveIDs[k]; invalidateMetadata && ok {
711			continue
712		}
713		result.metadata[k] = v
714	}
715	// Don't bother copying the importedBy graph,
716	// as it changes each time we update metadata.
717
718	return result
719}
720
721// shouldInvalidateMetadata reparses a file's package and import declarations to
722// determine if the file requires a metadata reload.
723func (s *snapshot) shouldInvalidateMetadata(ctx context.Context, originalFH, currentFH source.FileHandle) bool {
724	if originalFH == nil {
725		return currentFH.Identity().Kind == source.Go
726	}
727	// If the file hasn't changed, there's no need to reload.
728	if originalFH.Identity().String() == currentFH.Identity().String() {
729		return false
730	}
731	// If a go.mod file's contents have changed, always invalidate metadata.
732	if kind := originalFH.Identity().Kind; kind == source.Mod {
733		modfile, _ := s.view.ModFiles()
734		return originalFH.Identity().URI == modfile
735	}
736	// Get the original and current parsed files in order to check package name and imports.
737	original, _, _, originalErr := s.view.session.cache.ParseGoHandle(originalFH, source.ParseHeader).Parse(ctx)
738	current, _, _, currentErr := s.view.session.cache.ParseGoHandle(currentFH, source.ParseHeader).Parse(ctx)
739	if originalErr != nil || currentErr != nil {
740		return (originalErr == nil) != (currentErr == nil)
741	}
742	// Check if the package's metadata has changed. The cases handled are:
743	//    1. A package's name has changed
744	//    2. A file's imports have changed
745	if original.Name.Name != current.Name.Name {
746		return true
747	}
748	// If the package's imports have increased, definitely re-run `go list`.
749	if len(original.Imports) < len(current.Imports) {
750		return true
751	}
752	importSet := make(map[string]struct{})
753	for _, importSpec := range original.Imports {
754		importSet[importSpec.Path.Value] = struct{}{}
755	}
756	// If any of the current imports were not in the original imports.
757	for _, importSpec := range current.Imports {
758		if _, ok := importSet[importSpec.Path.Value]; !ok {
759			return true
760		}
761	}
762	return false
763}
764